正题
题目链接:https://www.luogu.com.cn/problem/AT3913
题目大意
给出一棵有边权的树,你每次可以选择一条链让所有的边异或上同一个值,求最少的操作次数使得所有边的权值都为000。
2≤n≤105,0≤w<162\leq n\leq 10^5,0\leq w<162≤n≤105,0≤w<16
解题思路
一条边的权值可以视为连接的两个点的权值异或,那么我们就可以把边边为点权了。
而链的异或就可以变成首尾两个点同时异或上一个值。
那么问题就变为了给nnn个数字你每次可以让两个同时异或上任意一个数,求最少操作次数使得它们都变为000。
那么显然一样的我们直接异或掉最优,那么现在就只剩下最多161616个不同的数了。
考虑状压dpdpdp,注意到如果一个集合能够操作到000那么这个集合肯定有所有数字的异或和为000,因为无论怎么操作序列的异或和都不会变。
那么理论上如果有xxx个数字,那么我们的操作次数上限是x−1x-1x−1,但是如果我们能把集合SSS分成两个集合T,S−TT,S-TT,S−T且这两个集合的异或和都为000,那么就变成了x−2x-2x−2,也就是最小的值在我们尽量分最多集合得到。
设fSf_SfS表示SSS最多分成多少个集合,然后O(316)O(3^{16})O(316)转移就好了。
时间复杂度:O(n+316)O(n+3^{16})O(n+316)
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
*/