AT3913-XOR Tree【状压dp】

正题

题目链接:https://www.luogu.com.cn/problem/AT3913


题目大意

给出一棵有边权的树,你每次可以选择一条链让所有的边异或上同一个值,求最少的操作次数使得所有边的权值都为\(0\)

\(2\leq n\leq 10^5,0\leq w<16\)


解题思路

一条边的权值可以视为连接的两个点的权值异或,那么我们就可以把边边为点权了。

而链的异或就可以变成首尾两个点同时异或上一个值。

那么问题就变为了给\(n\)个数字你每次可以让两个同时异或上任意一个数,求最少操作次数使得它们都变为\(0\)

那么显然一样的我们直接异或掉最优,那么现在就只剩下最多\(16\)个不同的数了。

考虑状压\(dp\),注意到如果一个集合能够操作到\(0\)那么这个集合肯定有所有数字的异或和为\(0\),因为无论怎么操作序列的异或和都不会变。

那么理论上如果有\(x\)个数字,那么我们的操作次数上限是\(x-1\),但是如果我们能把集合\(S\)分成两个集合\(T,S-T\)且这两个集合的异或和都为\(0\),那么就变成了\(x-2\),也就是最小的值在我们尽量分最多集合得到。

\(f_S\)表示\(S\)最多分成多少个集合,然后\(O(3^{16})\)转移就好了。

时间复杂度:\(O(n+3^{16})\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10,M=16;
int n,ans,a[N],v[M],c[1<<M],f[1<<M];
int main()
{
	scanf("%d",&n);
	for(int i=1,x,y,w;i<n;i++){
		scanf("%d%d%d",&x,&y,&w);
		a[x]^=w;a[y]^=w;
	}
	for(int i=0;i<n;i++)v[a[i]]++;
	int MS=(1<<16);
	memset(f,0xcf,sizeof(f));
	for(int s=1;s<MS;s++)
		for(int i=0;i<16;i++)
			if((s>>i)&1){
				c[s]=c[s-(1<<i)]^i;
				if(!c[s])f[s]=0;
				break;
			}
	int S=0;f[0]=0;
	for(int i=1;i<16;i++)
		ans+=(v[i]+1)/2,S|=((v[i]&1)<<i);
	if(!S)return printf("%d\n",ans)&0;
	for(int s=1;s<MS;s++){
		if(c[s])continue;
		for(int t=(s-1)&s;t>=(s^t);t=(t-1)&s)
			f[s]=max(f[s],f[t]+f[s^t]+1);
	}
	printf("%d\n",ans-f[S]-1);
	return 0;
}
/*
4
0 1 5
1 2 3
2 3 5
*/
posted @ 2021-12-18 13:54  QuantAsk  阅读(33)  评论(0编辑  收藏  举报