AtCoder Beginner Contest 152 - F - Tree and Constraints (容斥定理+树上路径的性质)

We have a tree with NN vertices numbered 11 to NN. The ii-th edge in this tree connects Vertex aiai and Vertex bibi.
Consider painting each of these edges white or black. There are 2N−12N−1 such ways to paint the edges. Among them, how many satisfy all of the following MM restrictions?

  • The ii-th (1≤i≤M)(1≤i≤M) restriction is represented by two integers uiui and vivi, which mean that the path connecting Vertex uiui and Vertex vivi must contain at least one edge painted black.

题意:

给定一个含有n个节点的树,每一条边可以给其染成黑色或者白色。

还有m个限制条件,每一个限制条件包括两个节点\(u,v\),限制为\(u,v\)节点之间的路径中必须包含至少一个涂成黑色的边。

问有多少种染色方法使其同时满足m个条件。

思路:

答案\(ans=v1-v2\)

\(v1\)代表所有染色方案,\(v2\)代表不满足条件的染色方案

我们知道\(v1=2^{n-1}\)

那么问题就直接转换成了求\(v2\)

\(A_i\)代表不满足第i个限制条件的方案数,那么\(v2\)就等于\(A_i\)的并集,即\(v2=\bigcup_1^{m} A_i\)

根据容斥定理:

那么我们就可以直接枚举\(m\)个限制的所有集合来求出\(v2\)

那么问题来了,怎么求\(A_i\)呢?

\(path_j\)代表从根节点(无根树的话就假定一个根)到第\(j\)个节点经过了哪些边。

因为\(n\leq 50\),所以\(path_i\)我们采用$long long $ 数据类型进行二进制状态压缩表示。

那么节点\(x,y\)之间的路径经历的边信息\(info=path_x \oplus path_y\)\(\oplus\) 是抑或符号。

那么对于枚举的每一个集合\(i\),把包含的所有限制的边信息\(info\)或起来后计算有多少个边,假设个数为\(num\)

那么这\(num\)个边染成白色时集合\(i\)才不满足条件。那么剩下\(free=n-1-num\)个边可以自由染色。

直接根据奇偶判断在容斥定理中公式的正负号即可。

代码:

int n;
int m;
std::vector<int> v[maxn];
ll path[maxn];
void dfs(int x, int pre, ll num)
{
	path[x] = num;
	for (auto y : v[x])
	{
		if (y == pre)
		{
			continue;
		}
		dfs(y, x, num | (1ll << y));
	}
}
ll info[maxn];

int main()
{
	//freopen("D:\\code\\text\\input.txt","r",stdin);
	//freopen("D:\\code\\text\\output.txt","w",stdout);
	n = readint();
	repd(i, 1, n - 1)
	{
		int x = readint();
		int y = readint();
		v[x].push_back(y);
		v[y].push_back(x);
	}
	dfs(1, 0, 0ll);
	m = readint();
	repd(i, 0, m - 1)
	{
		int x = readint();
		int y = readint();
		info[i] = (path[x] ^ path[y]);
	}
	int maxstate = (1 << m) - 1;
	ll ans = 0ll;
	repd(i, 0, maxstate)
	{
		ll num = 0ll;
		int cnt = 0;
		repd(j, 0, m - 1)
		{
			if (i & (1 << j)) {
				cnt++;
				num |= info[j];
			}
		}
		int free = n - 1 - __builtin_popcountll(num);
		ans += poww(2ll, free) * (cnt & 1 ? -1ll : 1ll);
	}
	printf("%lld\n", ans);
	return 0;
}