FZOJ 4108 世界树的考验
这题的转换非常的巧妙。
众所周知,树上一条路径点权异或有非常好的性质,可惜这道题是边权啊,这就需要一个转化:
- 设每个点的点权为与它相连的所有边的边权的异或和。那么使得每个点的点权为\(0\)就是题目要求达到的状态。
证明:考虑度数为\(1\)的点,显然与他们相连的边为\(0\),然后把他们删掉,又会有很多度数为\(1\)的点(类似于拓扑排序)……最后可以推出所有边都变成\(0\)。
那么,每次操作把一条路径的每一条边异或同一个数\(x\),相当于把路径两端的点权都异或\(x\),其他点权都不变(大家可以自己考虑考虑)。
现在问题变成了,给你若干个数(数值 \(\leq\) 15),每次给两个数异或上同一个数,问至少多少次可以把所有数变成\(0\)。
显然相同的数直接异或掉就可以了,所以可以看成若干个不同的数。设\(f[S]\)表示当状态为\(S\)时需要的最少操作数(\(i\)属于集合\(S\)当且仅当 \((1<<i\&S)!=0\) ),其实就是一个状态压缩,然后发现状态总数非常少(只有\(2^{16}\)种),所以可以直接一发记忆化搜索解决。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100009,INF=1<<30;
int siz[N],point[N],n,S,f[N*10];
void init()
{
scanf("%d",&n);
int x,y,z;
for (int i=1;i<n;i++)
{
scanf("%d %d %d",&x,&y,&z);
point[x]^=z,point[y]^=z;
}
for (int i=0;i<=n;i++)
siz[point[i]]++;
}
int dfs(int S)
{
if(S==0)
f[S]=0;
if(f[S]!=INF)
return f[S];
for (int i=0;i<=15;i++)
if(S&1<<i)
for (int j=0;j<=15;j++)
if(i!=j&&(S&1<<j))
{
int p=S^(1<<i)^(1<<j)^(1<<(i^j));
if(S&(1<<(i^j)))
f[S]=min(f[S],dfs(p)+2);
else
f[S]=min(f[S],dfs(p)+1);
}
return f[S];
}
void work()
{
int ans=0;
for (int i=1;i<=15;i++)
ans+=siz[i]/2,siz[i]&1?(S^=(1<<i)):0;
for (int i=0;i<1<<16;i++)
f[i]=INF;
printf("%d\n",ans+dfs(S));
}
int main()
{
init();
work();
return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!