CF1149E Election Promises
一、题目
二、解法
显然本题一定有终止态,感受一下就好了我不想证明。
本题大概是把 \(\tt nim\) 游戏放在 \(\tt DAG\) 上然后改了点游戏规则,我们还是主要从 \(xor\) 的角度思考。
首先把每个点按找 \(mex\) 分组,定义 \(a_u\) 为点 \(u\) 的组别:
\(a_u=mex\{a_v\ |\ (u,v)\in E\}\)
然后找稳态,也就是必胜方能维持必败方得到的状态。考虑对于组别 \(p\) 求出所有属于该组点的异或和 \(xor(p)\),那么稳态就是 \(\forall p,xor(p)=0\),我们猜测初始状态是稳态就先手必败,否则先手必胜。
我们只需要证明这两点,非稳态可以通过一步操作转到稳态,一步操作稳态只能转到非稳态:
- 第一点,我们找到满足 \(xor(p)\not=0\) 的最大的 \(p\),找到其中 \(h_u\) 和 \(xor(p)\) 最高位相同的一个点 \(u\),显然 \(h_u\) 是应该减少的,首先我们把 \(h_u\) 的最高位变成 \(0\),然后剩下的位就可以任取了,所以可以让 \(xor(p)\) 变成 \(0\);然后因为 \(mex\) 的性质,我们可以通过直连边改变任意编号 \(<p\) 的组别,所以可以让所有组的 \(xor(p)=0\)
- 第二点,任取一个点 \(u\),那么改变它的 \(h_u\) 会使 \(xor(a_u)>0\),因为 \(mex\) 的性质也无法通过直连边把它改回来,所以稳态只能转到非稳态。
那么跑拓扑排序就可以判断是否先手必胜了,如果先手必胜按上面的方法操作到稳态即可。
三、总结
解决非常规博弈问题的重要方法:找稳态,有稳态就有必胜策略。
分组是解决一个点对另一个点有影响的重要方法,博弈问题一定要多应用 \(mex\) 和 \(xor\) 这两个玄学玩意。
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,a[M],b[M],s[M],h[M],d[M],id[M];
vector<int> g[M];queue<int> q;
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
h[i]=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
g[u].push_back(v);d[v]++;
}
for(int i=1;i<=n;i++)
if(!d[i]) q.push(i);
while(!q.empty())
{
int u=q.front();q.pop();
id[++k]=u;//tuopu order
for(auto v:g[u])
{
d[v]--;
if(!d[v]) q.push(v);
}
}
for(int i=k;i>=1;i--)
{
int u=id[i];
for(auto v:g[u]) b[a[v]]=1;
for(int x=0;;x++) if(!b[x])
{
a[u]=x;
break;
}
for(auto v:g[u]) b[a[v]]=0;
}
for(int i=1;i<=n;i++)
s[a[i]]^=h[i];
int pd=0,rt=0;
for(int i=0;i<=n;i++)
if(s[i]) pd=1;
if(!pd)
{
puts("LOSE");
return 0;
}
puts("WIN");
for(int i=1;i<=n;i++) if(s[a[i]])
if(a[i]>=a[rt] && (s[a[i]]^h[i])<h[i])
rt=i;
for(auto v:g[rt])
if(s[a[v]]) h[v]=s[a[v]]^h[v],s[a[v]]=0;
h[rt]=s[a[rt]]^h[rt];
for(int i=1;i<=n;i++)
printf("%d ",h[i]);
}