[bzoj2819]Nim

14 篇文章 0 订阅
12 篇文章 0 订阅

Description

给出一棵树,每次修改一个点的值或询问x,y之间的路径上的数组成的石子游戏先手有没有必胜方案。(普通版SG)
n,m<=5*10^5

Solution

哎呀,dfs带一个参过~(≧▽≦)/~啦啦啦
虽然很对不起vfleaking爷,但是我也懒得改人工栈了。(受我深情一拜)
先来科(kou)普(hu)一下,先手必胜就是这条路径上的数的异或和不为0.
似乎刚开始想到了链剖,但是两个log的复杂度似乎过不了。
于是我们考虑直接维护前缀异或和,答案就是sg[x]^sg[y]^a[lca(x,y)]。
因为异或两次会抵消。
然后,我们每次修改会影响的只有x和它的子树。
那就可以用dfs序来维护了。
由于本蒟蒻很讨厌用bfs来求dfs序,于是就碾过去了。
可以用线段树打区间异或标记,单点查询。
也可以利用树状数组的前缀和功能,先把询问查分,然后把每个询问区间的修改变成两次单点修改。
你喜欢就好

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 500005
using namespace std;
int n,m,x,y,l,tot,a[N],f[N][19],d[N],sg[N],in[N],out[N];
int t[N*2],next[N*2],last[N];
char s[1];
void add(int x,int y) {
    t[++l]=y;next[l]=last[x];last[x]=l;
}
void change(int x,int v) {
    for(;x<=n;x+=x&-x) sg[x]^=v;
}
int find(int x) {
    int ans=0;
    for(;x;x-=x&-x) ans^=sg[x];
    return ans;
}
void dfs(int x) {
    fo(j,1,18) f[x][j]=f[f[x][j-1]][j-1];
    in[x]=++tot;
    rep(i,x) if (t[i]!=f[x][0]) {
        f[t[i]][0]=x;d[t[i]]=d[x]+1;
        dfs(t[i]);
    }out[x]=tot;
}
int lca(int x,int y) {
    if (d[x]<d[y]) swap(x,y);
    fd(j,18,0) if (d[f[x][j]]>d[y]) x=f[x][j];
    if (d[x]!=d[y]) x=f[x][0];
    fd(j,18,0) if (f[x][j]!=f[y][j]) x=f[x][j],y=f[y][j];
    if (x!=y) return f[x][0];else return x;
}
int main() {
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i]);
    fo(i,1,n-1) scanf("%d%d",&x,&y),
    add(x,y),add(y,x);
    d[1]=1;dfs(1);
    fo(i,1,n) change(in[i],a[i]),change(out[i]+1,a[i]);
    for(scanf("%d",&m);m;m--) {
        scanf("%s%d%d",s,&x,&y);
        if (s[0]=='Q') {
            int z=lca(x,y);
            if (find(in[x])^find(in[y])^a[z]) printf("Yes\n");
            else printf("No\n");
        } else {
            change(in[x],a[x]),change(out[x]+1,a[x]);
            a[x]=y;
            change(in[x],a[x]),change(out[x]+1,a[x]);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值