题
题意简述
给一棵有 \(N\) 个节点的树,节点编号从 \(0\) 到 \(N-1\),
树边编号从 \(1\) 到 \(N-1\)。第 \(i\) 条边连接节点 \(x_i\) 和 \(y_i\),其权值为 \(a_i\)。
你可以对树执行任意次操作,每次操作选取一条链和一个非负整数 \(x\),将链上的边的权值与 \(x\) 异或成为该边的新权值。
问最少需要多少次操作,使得所有边的权值都为 \(0\)。
题目分析
感觉很典的东西,但是我不会,怎么回事呢?
如果直接操作边权,题目就很不可做,因为改一条链太麻烦了。
我们考虑,在修改一条链的边权的过程中,有什么东西的变化量是很少的呢?
一点也不显然,就是一个点邻接的所有边的权值异或和。一条链上除端点外所有点度数都是 \(2\),所以一条链异或一个数 \(x\) 直接等价于将任意两个点的权值异或 \(x\)。这样就简单多了。而且这两个东西也是一一对应的(剥叶子的方式互相转化)。
下面就是暴力的部分了。
首先权值只有 \(16\) 种,每种两两消去肯定不劣,这么做完之后每种最多只剩一个了。就可以状压。设 \(f_S\) 为将 \(S\) 这个集合消成 \(0\) 至少要多少次操作。首先异或和不为 \(0\) 时 \(f_S\) 为 \(\infty\),否则 \(f_S\) 至多为 \(\left|S\right| - 1\)。或者 \(f_S = f_T + f_{S/T}(T \in S)\)。
直接枚举子集,时间复杂度 \(O(n + 3^V)\)。
#include<bits/stdc++.h>
#define pii std::pair<int,int>
#define mkp std::make_pair
#define fir first
#define sec second
typedef long long ll;
inline void rd(){}
template<typename T,typename ...U>
inline void rd(T &x,U &...args){int ch=getchar();T f=1;x=0;while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();x*=f;rd(args...);
}
const int N=1e5+5,V=20;
int n,a[N],k[V],m,b[V],f[1<<16],ans;
signed main(){rd(n);for(int i=1;i<n;i++){int x,y,z;rd(x,y,z);++x,++y;a[x]^=z,a[y]^=z;}for(int i=1;i<=n;i++){if(k[a[i]]&&a[i])++ans;k[a[i]]^=1;}for(int i=0;i<16;i++)if(k[i])b[++m]=i;memset(f,0x3f,sizeof f);f[0]=0;for(int t=1;t<(1<<m);t++){int xors=0;for(int i=1;i<=m;i++)if(t>>(i-1)&1)xors^=b[i];if(xors==0)f[t]=__builtin_popcount(t)-1;for(int j=(t-1)&t;j;j=(j-1)&t)f[t]=std::min(f[t],f[j]+f[t^j]);}printf("%d\n",ans+f[(1<<m)-1]);return 0;
}