写过字母版的trie树,却不知道还有这等妙用
题目链接
考虑到异或的性质,即异或两次相同的数,结果与原来保持不变,
很容易证明,a ^ b ^ b , 又 b ^ b= 0, 所以 a ^ b ^ b=a ^ 0 = a,所以对于每个节点求一个到根节点的异或路径和dis[v],u->v的异或路径和就很好表示,即为dis[u]^dis[v],如何更新最大又不用n ^ 2枚举呢?
trie数就派上用场
对于每个dis[v]都像这样插入trie树,对于样例即变成这样,
每次插入时加一个比较操作,即针对已有的节点进行比较,对于一个01串,要想找到与其他串的异或竟可能大,采取贪心策略,即当前位要尽可能不同(0希望对1,1希望对0),从高位开始,逐位比较
贪心策略很好证明.
1000=2^3,0111=2 ^ 3 -1,所以多一位是1其他全是零总比少一位其他全是1大。
直接上代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<algorithm>
#define MAXN 100010
using namespace std;
int dis[MAXN],maxlog=32,cnt,ans,n;
struct Edge{int to,l;};
vector<Edge> E[MAXN];
struct Node{int son[2],f;}N[32*MAXN];
void dfs(int u,int fa,int yh){//维护dis
for(int i=0;i<E[u].size();i++){
int v=E[u][i].to,len=E[u][i].l;
if(v==fa) continue;
dis[v]=yh^len;
dfs(v,u,yh^len);
}
}
void insert(int u){//插入
int now=0;
for(int i=maxlog-1;i>=0;i--){
int t=(dis[u]>>i)&1;
if(!N[now].son[t]) N[now].son[t]=++cnt;
now=N[now].son[t];
}
}
int Query(int u){//比较
int now=0,sum=0;
for(int i=maxlog-1;i>=0;i--){
bool t=(dis[u]>>i)&1;
if(N[now].son[!t]) sum+=(1<<i),now=N[now].son[!t];
else now=N[now].son[t];
}
return sum;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v,w; scanf("%d%d%d",&u,&v,&w);
E[u].push_back((Edge){v,w});
E[v].push_back((Edge){u,w});
}
dfs(1,0,0);
for(int i=1;i<=n;i++){
insert(i);
ans=max(ans,Query(i));
}
printf("%d\n",ans);
return 0;
}