NOIP模拟(10.22)T3 树

来源:互联网 发布:tv软件市场 编辑:程序博客网 时间:2024/05/22 17:41
 

题目背景:

10.22 NOIP模拟作业T3

分析:状压DP

首先证明一些东西。

1、显然,编号越小的点深度越深越好

证明:显然,如果一个点的编号很小,它的深度越高,意味着它影响的叶子节点越多,那么显然就会很不优。

2、对于以一个点为根的子树,它的编号一定是连续的。

证明:假设对于一个点x它子树的sizey,但是编号为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;}

原创粉丝点击