本文主要是介绍NOIP模拟(10.22)T3 树,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
题目背景:
分析:状压DP
首先证明一些东西。
1、显然,编号越小的点深度越深越好
证明:显然,如果一个点的编号很小,它的深度越高,意味着它影响的叶子节点越多,那么显然就会很不优。
2、对于以一个点为根的子树,它的编号一定是连续的。
证明:假设对于一个点x它子树的size为y,但是编号为1 ~ y - 1,和y + 1,那么必然有另一个地方的编号为y,因为我们已经说过,如果,将编号y放在x当中,因为y为整个子树当中的最大编号,那么它不会影响x子树中叶子节点的路径最小值,但是将y放在其他地方因为它是目前最小的编号,所以一定会影响一些叶子节点的取值,那么显然是不优秀的,所以对于一个子树它的编号一定是连续的。
说到这里想必思路就比较清晰了,我们的编号策略就是,从小到大编号,从叶子节点标起,一个非叶结点被标记,当且仅当,它的整个子树均被标记,那么现在影响答案的因素就只有一个了,即叶子节点的标号顺序,我们可以用f[i]状压表示,当前标记了哪些叶子节点,这些叶子节点的最优答案是多少。因为对于每一个状态我们都需要dfs判定,当前被确定了哪些编号,那么复杂度是O(2 ^ 叶子节点数 * 树的大小)这样显然复杂度偏高,考虑该怎么优化一下,我们发现,对于一个非叶非根节点,如果它只有一个儿子,那么它是可以和它的儿子放在一起的,儿子一旦标记,它就需要标记,那么我们可以在一开始的时候,把这种情况的节点,也就是一条链缩到一起,这样树的大小就很小了,(最大的情况为满二叉树),可以搞定。
具体实现:
g[i]表示,在状态i中存在的叶子节点全部标记之后,这i个权值之积最大为g[i],枚举到i状态时,dfs找寻当前能够被确定的已经标记的节点有sum个,那么下一个被标记的叶子节点的值一定为sum + 1(上面已经证明过了),所以我们可以将g[i] à g[i | (1<< j)] ((i & (1 << j)) == 0)然后最后的g[i]就是答案啦。
注意:因为答案过大需要取模,所以,我们可以存储两个值g[i],f[i],g[i]为答案本身的值,用double存储,来用于比较,f[i]为int即取好模的答案。
Source:
/*created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cctype>
#include <set>
#include <map>
#include <vector>
#include <queue>const int MAXX = 20;
const int MAXN = 100000 + 10;
const int mod = 1000000000 + 7;int n, x, y, sum;
std::vector<int> leaves;
std::vector<int> ori_edge[MAXN], edge[MAXN];
int f[1 << MAXX], dep[MAXN], num[MAXN], size[MAXN];
double g[1 << MAXX];
bool use[MAXN];inline void add_ori_edge(int x, int y) {ori_edge[x].push_back(y), ori_edge[y].push_back(x);
}inline void read_in() {scanf("%d", &n);for (int i = 1; i < n; ++i) scanf("%d%d", &x, &y), add_ori_edge(x, y);
}inline void get_point(int cur, int fa) {/*标记有意义的节点*/dep[cur] = dep[fa] + 1, size[cur] = 1;int cnt = 0;for (int p = 0; p < ori_edge[cur].size(); ++p) {int v = ori_edge[cur][p];if (v != fa) get_point(v, cur), size[cur] += size[v], ++cnt;}if (cnt != 1) use[cur] = true;if (cnt == 0) leaves.push_back(cur);
}inline void build_new_tree(int cur, int fa, int last) {/*建立新的树*/ if (use[cur] && last) edge[last].push_back(cur);if (use[cur]) last = cur;for (int p = 0; p < ori_edge[cur].size(); ++p) {int v = ori_edge[cur][p];if (v != fa) build_new_tree(v, cur, last);}
}inline void dfs(int cur, int fa) {/*查找多少个点的标号被确定*/ if (edge[cur].size() == 0) return (void)(sum += num[cur]);num[cur] = 0;for (int p = 0; p < edge[cur].size(); ++p) {int v = edge[cur][p];if (v != fa) {dfs(v, cur);if (num[v] == size[v]) {num[cur] += num[v] + dep[v] - dep[cur] - 1;sum += dep[v] - dep[cur] - 1;}}}if (num[cur] == size[cur] - 1) num[cur]++, sum++;
}inline void solve() {use[1] = true, get_point(1, 0), build_new_tree(1, 0, 0);for (int i = 0; i < leaves.size(); ++i) f[1 << i] = g[1 << i] = 1; for (int i = 0, s = (1 << int(leaves.size())); i < s; ++i) {for (int j = 0; j < leaves.size(); ++j) num[leaves[j]] = (i & (1 << j)) ? 1 : 0;sum = 0, dfs(1, 0);double max = g[i] * (double)(sum + 1);int ans = (long long)f[i] * (long long)(sum + 1) % mod;for (int j = 0; j < leaves.size(); ++j)if ((i & (1 << j)) == 0 && g[i | (1 << j)] < max)g[i | (1 << j)] = max, f[i | (1 << j)] = ans; }std::cout << f[(1 << int(leaves.size())) - 1];
}int main() {read_in();solve();return 0;
}
这篇关于NOIP模拟(10.22)T3 树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!