AtCoder Beginner Contest 152 - F - Tree and Constraints (容斥定理+树上路径的性质)
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;
}