2017 ACM西安网络赛 G题 Xor

题意:
给出一棵树,每个节点有个val,q个询问。
询问格式:u, v, k, 从u到v的路径, 每个k个点取一个,求所有点的val值异或和。
如果没有隔k个取1个的限制的话,那么做法是很显然的,dfs一遍,求出所有点到根的异或和,ans=xor(u, root)^xor(v, root)^(lca(u, v),root)。
现在有步长的限制,取m=sqrt(n),
1)预处理出步长k<=m时每个点到根的异或和。
2)当询问k>m时,暴力求取。

虽然k>m时是暴力求取,但这建立在我们能够快速求得anc[u][k] (u向上k级的祖先)的基础上。为了快速求取anc[u][k],可以用倍增或者树链剖分实现。
以下的AC代码基于树链剖分实现,
代码含注释。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<iostream>
#define debug puts("Infinity is awesome!")
#define mm(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
const int maxn=5e4+50;
const LL mod=1e9+7;
const double eps=1e-8;
struct Edge{
    int to, next;
};
Edge edges[2*maxn];
int n, m, q;
int head[maxn], ecnt;
int depth[maxn], anc[maxn][233];
int cnt;
int siz[maxn], son[maxn], top[maxn], tid[maxn], rak[maxn];
int val[maxn], jut[maxn][233];
void Init(){
    ecnt=0; cnt=0;
    mm(head, -1);
    mm(jut, 0);
}
void AddEdge(int u,int v){
    edges[ecnt]=Edge{v, head[u]};
    head[u]=ecnt++;
}
void Build(int u,int f,int d){
    int maxc=0, maxb=0;
    siz[u]=1; depth[u]=d;
    anc[u][1]=f;
    for(int i=head[u];i!=-1;i=edges[i].next){
        int v=edges[i].to;
        if(v==f) continue;
        Build(v, u, d+1);
        siz[u]+=siz[v];//更新子树大小
        if(siz[v]>maxc){//找出每个节点的最大子儿子
            maxc=siz[v];
            maxb=v;
        }
    }
    son[u]=maxb;//记录最大子儿子
}
void Dfs(int u,int tp){//树链剖分
    top[u]=tp; tid[u]=++cnt;//记录重链的最高点,剖分后的ID
    rak[cnt]=u;//ID对应的节点
    for(int i=1;i<=m;i++)//从u跳i步,直到根节点的异或和
        jut[u][i]=jut[anc[u][i]][i]^val[u];
    if(son[u]) Dfs(son[u], tp);//重边向下构造重链
    for(int i=head[u];i!=-1;i=edges[i].next){
        int v=edges[i].to;
        if(v==anc[u][1]||v==son[u]) continue;
        Dfs(v, v);//轻边向下构造轻链,最高点为v
    }
}
int Lca(int x,int y){//根据重链求lca
    while(top[x]!=top[y]){
        if(depth[top[x]]<depth[top[y]]) swap(x,y);
        x=anc[top[x]][1];
    }
    if(depth[x]<depth[y]) swap(x,y);
    return y;
}
int Jump(int x,int k){//从x向上跳k步,返回x的k级祖先
    if(depth[x]<=k) return 0;
    int y=top[x];
    while(x&&k>=depth[x]-depth[y]+1){
        k-=depth[x]-depth[y]+1;
        x=anc[y][1], y=top[x];
    }
    if(x==0) return 0;
    return rak[tid[x]-k];
}
int Get(int x,int y,int k,int s){//返回x到y,步长为k的异或和
    int ret=0;                   //s取值0或1,lca的val只能被异或一次,需要异或lca时s=0
    if(depth[x]<depth[y]+s) return 0;
    if(k<=m){
        ret^=jut[x][k];
        while(depth[x]>=depth[y]+s){
            x=Jump(x, k);
        }
        ret^=jut[x][k];
        return ret;
    }
    ret^=val[x];
    while(depth[x]>=depth[y]+k+s){
        x=Jump(x, k);
        ret^=val[x];
    }
    return ret;
}
int main(){
    int T;
    int cas=0;
    int u, v, k;

    //scanf("%d", &T);
    //while(T--){
    while(~scanf("%d%d", &n, &q)){
        m=(int)sqrt(n+0.5);
        Init();
        for(int i=1;i<n;i++){
            scanf("%d%d", &u, &v);
            AddEdge(u, v);
            AddEdge(v, u);
        }
        for(int i=1;i<=n;i++){
            scanf("%d", &val[i]);
        }
        Build(1, 0, 1);
        for(int i=1;i<=n;i++)
            for(int j=2;j<=m;j++)
            anc[i][j]=anc[anc[i][j-1]][1];
        Dfs(1, 1);
        for(int i=0;i<q;i++){
            scanf("%d%d%d", &u, &v, &k);
            int z=Lca(u, v);
            int dis=depth[u]+depth[v]-2*depth[z];
            int ans=0;
            ans^=Get(u, z, k, 0); //printf("ans=%d\n", ans);
            if(dis%k) v=Jump(v, dis%k); //printf("v=%d\n", v);
            ans^=Get(v, z, k, 1);
            printf("%d\n", ans);
        }
    }

    return 0;
}

原题(BZOJ4381)题解链接。
区别在于求和,求异或和。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值