JZOJ 5693. 【GDSOI2018模拟4.26】对战

题目

有一棵树。树上的点有2种颜色,两个人轮流选择一个白色的点,将其到根的路径上的点
全部染黑。不能操作者输。
开始树上的节点并非全白。

水法

这并不是题解。但是我从水法中学到了一点东西。
考虑只保留白色的点,重新建树。会得到一个森林。
考虑一个点的 sg 值,它只和它的子树有关(这是一个很关键的地方),因为只有选择其子树内的节点进行操作才是不会互相干扰的。
所以,选择 x 子树内的一个节点y,计算与 x到y这条路径上的点 有边相连的点(不含 x到y这条路径上的点)的 sg 值。
然后将这些 sg 值异或起来,这个是此游戏的子游戏的 sg 值。
然后再去 mex
如果森林中所有根节点的 sg 的异或和=0,则输。
否则,计算与 根到某个点的路径上的点 有边相连的点(根到某个点的路径上的点)的 sg 的异或和。如果为0,说明选择之后,对方必败。

心得

重新说一遍:
游戏→子游戏,求 sg 的异或和。
状态→子状态,求 sg[] mex

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100010
#define LL long long
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int to,next;
};note edge[N*2];
int tot,head[N];
int c[N];
int i,j,k,l,n,m;
int nim;
int fa[N],Fa[N],ans[N];
int o[N],cnt[N],CNT;
int sg[N];
int u,v,X;
int read(){
    int fh=1,rs=0;char ch;
    while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')rs=(rs<<3)+(rs<<1)+(ch^'0'),ch=getchar();
    return fh*rs;
}
void write(int x){
    if(x>9)write(x/10);
    P(x%10+'0');
}
void lb(int x,int y){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
void dfs(int x,int y){
    if(!c[x])Fa[x]=y;
    int i;
    for(i=head[x];i;i=edge[i].next)
        if(edge[i].to^fa[x]){
            fa[edge[i].to]=x;
            if(c[x])dfs(edge[i].to,y);
               else dfs(edge[i].to,x);
        }
}
void check(int x){
    int i;
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
    cnt[k]=CNT;
    for(i=head[x];i;i=edge[i].next){
        k^=sg[edge[i].to];
        check(edge[i].to);
        k^=sg[edge[i].to];
    }
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
}
void dg2(int x){
    int i;
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
    if(!k)ans[++ans[0]]=x;
    for(i=head[x];i;i=edge[i].next){
        k^=sg[edge[i].to];
        dg2(edge[i].to);
        k^=sg[edge[i].to];
    }
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
}
void dg(int x){
    int i;
    if(!o[x]){
        sg[x]=1;
        return;
    }
    for(i=head[x];i;i=edge[i].next)
        if(edge[i].to^Fa[x])
            dg(edge[i].to);
    X=x;
    CNT++;
    k=0;
    check(x);
    for(sg[x]=0;cnt[sg[x]]==CNT;sg[x]++);
}
int main(){
    freopen("combat.in","r",stdin);
    freopen("combat.out","w",stdout);
    n=read();
    fo(i,1,n)c[i]=read();
    fo(i,1,n-1){
        u=read();v=read();
        lb(u,v);lb(v,u);
    }
    dfs(1,0);
    memset(edge,0,sizeof(edge));
    memset(head,0,sizeof(head));
    tot=0;
    fo(i,2,n)if(Fa[i]){
        lb(Fa[i],i);
        o[Fa[i]]++;
    }
    nim=0;
    fo(i,1,n)if(!c[i]&&!Fa[i]){
        dg(i);
        nim^=sg[i];
    }
    if(!nim)printf("-1");
    else{
        fo(i,1,n)if(!Fa[i]&&!c[i])lb(0,i);
        k=0;
        dg2(0);
        sort(ans+1,ans+ans[0]+1);
        fo(i,1,ans[0])if(ans[i])write(ans[i]),P('\n');
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值