1. 程式人生 > 其它 >AT3913-XOR Tree【狀壓dp】

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
*/