标签:那么 XOR 16 状压 Tree 异或 权值 集合 include
正题
题目链接: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
*/
标签:那么,XOR,16,状压,Tree,异或,权值,集合,include 来源: https://www.cnblogs.com/QuantAsk/p/15704904.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。