字典树--Xor问题

字典树大家都知道吧,如果不知道可以看这里,我的模板写得还是不错的:
字符串–Trie树(字典树)


接下来我们先看一个问题,通过这个问题来了解Xor这个运算的基本性质:
洛谷 P2420 让我们异或吧
题目大意:给定一棵带权树,多次询问两点之间边权异或和。
性质:a^x^x=a,其中^代表Xor运算,证明略。
利用这条性质,我们可以任取一点为根,通过一次dfs算出每个点到根节点的异或和val[u],假设每次询问的点是u,v,则答案就是val[u]^val[v]。
过程: val(u,v)
=val(u,lca) Xor val(v,lca)
=val(u,root) Xor val(lca,root) Xor val(v,root) Xor val(lca,root)
=val(u,root) Xor val(v,root)
此题代码:

//Âå¹È2420 ÈÃÎÒÃÇÒì»ò°É 
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
inline int read(){
    int x=0;char ch=' ';
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x;
}
struct edge{
    int to,next,w;
}e[200001];
int n,m,tot;
int head[100001];
int v[100001];
int st[100001][20];
int dep[100001];
void dfs(int x,int fa){
    st[x][0]=fa;
    for(int i=head[x];i;i=e[i].next){
        int u=e[i].to;
        if(u==fa)continue;
        v[u]=v[x]^e[i].w;
        dep[u]=dep[x]+1;
        dfs(u,x);
    }
}
inline void addedge(int x,int y,int l){
    e[++tot].to=y;e[tot].next=head[x];e[tot].w=l;head[x]=tot;
}
int main(){
    n=read();
    for(int i=1;i<=n-1;i++){
        int x=read(),y=read(),l=read();
        addedge(x,y,l);addedge(y,x,l);
    }
    dfs(1,0);
    m=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        int ans=v[x]^v[y];
        printf("%d\n",ans);
    }
    return 0;
}

接下来就是正题了——如何用字典树来解决Xor问题?
题目链接:Xor Sum
整体思路:Trie树+贪心
我们要选出一个数来使它和另一个数的异或和最大,那么当然要转二进制来看了。
转了二进制之后我们可以发现高位的值比所有低位的值相加还要大,那么就有贪心思路了:
建立一棵01Trie(每个点只有0和1两个儿子),然后将每个初始数字从高位到低位按位插入进来,然后每次查询时每一步尽量走和它那一位不同的方向,最后输出即可。
此题代码:

//hdu4825 Xor Sum
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
inline int read(){
    int x=0;char ch=' ';
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x;
}
int n,m,tot;
int ch[3100001][2];
int val[3100001];
void insert(int x){
    int u=0;
    for(int i=31;i>=0;i--){
        int c=(x>>i)&1;
        if(!ch[u][c]){
            ch[u][c]=++tot;
        }
        u=ch[u][c];
    }
    val[u]=x;
}
int find(int x){
    int u=0;
    for(int i=31;i>=0;i--){
        int c=(x>>i)&1;
        if(ch[u][c^1]){
            u=ch[u][c^1];
        }
        else{
            u=ch[u][c];
        }
    }
    return val[u];
}
int main(){
    int T=read();
    int k=1;
    while(T--){
        printf("Case #%d:\n",k++);
        memset(ch,0,sizeof(ch));
        memset(val,0,sizeof(val));
        tot=0;
        n=read();m=read();
        for(int i=1;i<=n;i++){
            insert(read());
        }
        for(int i=1;i<=m;i++){
            printf("%d\n",find(read()));
        }
    }
    return 0;
}

接下来我们将第一个问题和第二个问题结合在一起来看一个题;
题目链接:The xor-longest Path
题目大意:给定一棵树,求最大异或路径。
思路:我们可以每次固定一个点,寻找和它异或路径最大的点,更新答案。这时问题就转化成了在树上已知一个点如何找另一个点和它异或路径最大。
结合T1和T2,那么我们可以表示出两点间的异或路径:val(u)^val(v),其中因为我们已经固定了一个点了,那么就是要找一个值和已知值异或值最大了,这就是T2的问题。
方案就很明显了,dfs求出每个点到根节点的异或值,然后将这些值插入到一棵01Trie中,枚举每个点找从这个点出发的最大异或路径,并更新答案。
此题代码:

//poj3764 The xor-longest Path
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int v[100001];
int ch[3100001][2];
int n;
struct edge{
    int to,next,w;
}e[200001];
int head[100001];
void dfs(int x,int fa){
    for(int i=head[x];i;i=e[i].next){
        int u=e[i].to;
        if(u==fa)continue;
        v[u]=v[x]^e[i].w;
        dfs(u,x);
    }
}
int tot;
inline void addedge(int x,int y,int l){
    e[++tot].to=y;
    e[tot].next=head[x];
    head[x]=tot;
    e[tot].w=l;
}
void insert(int x){
    int u=0;
    for(int i=30;i>=0;i--){
        int c=(x>>i)&1;
        if(!ch[u][c]){
            ch[u][c]=++tot;
        }
        u=ch[u][c];
    }
}
int find(int x){
    int u=0;int ans=0;
    for(int i=30;i>=0;i--){
        int c=(x>>i)&1;
        if(ch[u][c^1]){
            ans+=(1<<i);
            u=ch[u][c^1];
        }
        else{
            u=ch[u][c];
        }
    }
    return ans;
}
int main(){
    while(scanf("%d",&n)==1){
        memset(ch,0,sizeof(ch));
        memset(v,0,sizeof(v));
        memset(head,0,sizeof(head));
        tot=0;
        for(int i=1;i<=n-1;i++){
            int x,y,l;
            scanf("%d %d %d",&x,&y,&l);
            addedge(x,y,l);
            addedge(y,x,l);
        }
        dfs(0,-1);
        for(int i=0;i<n;i++){
            insert(v[i]);
        }
        tot=0;
        int ans=0;
        for(int i=0;i<n;i++){
            ans=max(ans,find(v[i]));
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值