这题的转换非常的巧妙。
众所周知,树上一条路径点权异或有非常好的性质,可惜这道题是边权啊,这就需要一个转化:
- 设每个点的点权为与它相连的所有边的边权的异或和。那么使得每个点的点权为(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
",ans+dfs(S));
}
int main()
{
init();
work();
return 0;
}