NOIP模拟(10.22)T3 树

 

题目背景:

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值