bzoj2819 Nim

http://www.lydsy.com/JudgeOnline/problem.php?id=2819

题意:对一棵树单点修改和询问链上各点权作为Nim游戏的初始局面先手是否有必胜策略。n,q<=500,000。

众所周知,一个Nim游戏的初始局面是先手必胜的当前仅当各堆石子数的异或结果不为0,因此题目等价于单点修改和询问链异或结果。

先考虑没有修改的情况,由于异或运算具有可加减性,很容易想到这个做法:任取一点为根,用dfs预处理好每个点到根的路径上各点的异或结果f[]

当求点x到点y的路径上各点的异或结果,只需先求出它们的最近公共祖先u,那么f[x]^f[y]^f[u]^v[u]就是答案,其中v[]表示点权。

也就是说,求链异或结果只需知道f[]v[]v[]的维护就简单了,而f[]的维护则显得有些棘手。

注意到,点x的权值改变能够影响f[y]的值,当且仅当xy的祖先,即y在以x为根的子树中。

也就是说,修改点权后,我们要维护f[]值的点都在以被修改点为根的子树中。而且,是将子树中的每个点都异或上一个相同的值。

如果我们记录每个点在dfs时被访问的时间戳,即dfs序,那么我们可以发现,每个子树中的所有结点对应的dfs序恰好是连续的一段。

也就是说,我们能够将修改子树转化为修改区间a[l...r]。同理,询问f[]也只要询问其dfs序对应的值a[]

至于区间修改和单点询问,这很容易用线段树或差分序列的树状数组实现。

当然,这题用树链剖分做也是可行的,不过可能会需要降低一下常数,且编程复杂度也提高了。

代码:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<ctime>
#define rpt(i,l,r) for(i=l;i<=r;i++)
#define rpd(i,r,l) for(i=r;i>=l;i--)
#define M 500005
using namespace std;
int v[M],a[M]={0};
int n,i,j,k,xx,yy;
char ss[2];
int x[M],y[M],s[M]={0},t[M*2];
int f[M][19],l[M],r[M],d[M],q[M],p[M];
int tot=0;
int source;
int read(){
         intx=0,f=1;char ch=getchar();
         while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
         while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
         returnx*f;
}
int lowbit(int x){
    return x&(x^(x-1));
}
int sum(int x){
    if(x) return a[x]^sum(x-lowbit(x));
    else return 0;
}
void modify(int x,int y){
     if(x>n) return;
     a[x]^=y;
     modify(x+lowbit(x),y);
}
void dfs(int x){
     l[x]=++tot;p[tot]=x;
     for(int i=s[x-1]+1;i<=s[x];i++)if(f[x][0]!=t[i]){f[t[i]][0]=x;d[t[i]]=d[x]+1;q[t[i]]=q[x]^v[t[i]];dfs(t[i]);}
     r[x]=tot;
}
int lca(int x,int y){
    int k,i;if(d[x]>d[y]) k=x,x=y,y=k;
    k=d[y]-d[x];rpt(i,0,18) if(k&(1<<i)) y=f[y][i];
    if(x==y) return x;
    rpd(i,18,0) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main(){
         freopen("2819in.txt","r",stdin);
         freopen("2819myout.txt","w",stdout);
    n=read();v[0]=0;rpt(i,1,n) v[i]=read();
    rpt(i,1,n-1){x[i]=read();y[i]=read();s[x[i]]++;s[y[i]]++;}
    rpt(i,1,n) s[i]+=s[i-1];rpd(i,n,1) s[i]=s[i-1];
    rpt(i,1,n-1) t[++s[x[i]]]=y[i],t[++s[y[i]]]=x[i];

    source=rand()%n+1;
    f[source][0]=d[source]=0;q[source]=v[source];dfs(source);

    rpt(i,1,n) modify(l[i],q[i]^q[p[l[i]-1]]);

    rpt(j,1,18) rpt(i,1,n) if(f[i][j-1]) f[i][j]=f[f[i][j-1]][j-1];

    k=read();
    while(k--){
              scanf("%s",ss);xx=read();yy=read();
              if(ss[0]=='C'){modify(l[xx],v[xx]^yy);if(r[xx]<n)modify(r[xx]+1,v[xx]^yy);v[xx]=yy;}

               else if(sum(l[xx])^sum(l[yy])^v[lca(xx,yy)])puts("Yes"); else puts("No");

    }
    fclose(stdin);
    fclose(stdout);
}

 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值