模板(AcWing 143.最大异或对)
那么这个题目就是利用Trie树来优化暴力枚举做法,我们对于任意的一个a[i],要让其与另一个数的异或和最大,想到利用贪心的做法,从高位到低位枚举,尽量在树中寻找与a[i]同数位但数值恰好相反的节点。代码如下:
#include<iostream>
using namespace std;
const int N=3e6+5;
int a[N];
int son[N][2],idx;
void insert(int x){
int p=0;
for(int i=30;~i;i--){
int &s=son[p][x>>i&1];
if(!s) s=++idx;
p=s;
}
}
int find(int x){
int p=0,res=0;
for(int i=30;~i;i--){
int s=x>>i&1;
if(son[p][!s]){//每次尽量走相反的节点
res+=1<<i;
p=son[p][!s];
}
else{
p=son[p][s];
}
}
return res;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
insert(a[i]);
}
int ans=-1;
for(int i=1;i<=n;i++){
ans=max(ans,find(a[i]));
}
cout<<ans<<endl;
}
应用:AcWing 144.最长异或值路径
所谓的路径,可以想办法把它转化成两点或是两数之间的关系。那么中间的关联在于什么呢?当然是根节点。两点之间的异或路径长度,就是它们各自到根节点的异或路径长度的异或和(我这里是先简单地考虑到了两个节点在根节点两旁不同的子树上的情况,后面再想到路径重合的情况)。那么怎么证明这个算法的正确性呢?我们要发现异或运算的一些性质:
1.异或运算满足交换律与结合律
2.a xor a=0
3.a xor 0=a
这样就说明了两点间重合的路径的异或值为0,不会对结果造成影响。所以我们得出了一个算法:先用dfs求出所有节点到根节点的异或路径长,然后套用最大异或对模板求解即可。
代码如下:
#include<iostream>
using namespace std;
const int N=3e6+5;
typedef long long ll;
int head[N],to[2*N],ne[2*N],idx;
ll a[N],w[N];
void add(int x,int y,ll z){
ne[++idx]=head[x];
to[idx]=y;
w[idx]=z;
head[x]=idx;
}
void dfs(int u,int fa){
for(int i=head[u];i;i=ne[i]){
int j=to[i];
if(j!=fa){
a[j]=a[u]^w[i];
dfs(j,u);
}
}
}
int son[N][2];
void insert(ll x){
int p=0;
for(int i=31;i>=0;i--){
int u=x>>i&1;
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
}
ll find(ll x){
int p=0;
ll ans=0;
for(int i=31;i>=0;i--){
int u=x>>i&1;
if(son[p][!u]){
ans+=1<<i;
p=son[p][!u];
}
else p=son[p][u];
}
return ans;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
ll z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
dfs(0,-1);
idx=0;
for(int i=1;i<n;i++){
insert(a[i]);
}
ll ans=-1;
for(int i=1;i<=n;i++){
ll sb=find(a[i]);
ans=max(ans,sb);
}
cout<<ans;
return 0;
}