文章目录
E. Election Promises
题意:
给定一棵有向树,每个点有一个权值,两个人进行操作,可以选择任意一个节点,将其权值减小为一个非负数,并且可以将其子节点修改为任意的权值,判定是否先手必赢,如果先手必赢,给定一个先手的方案
分析:
1 我们看到有向树,就可以联想到拓扑排序有关的问题,或者直接dfs进行图的遍历,
2 博弈问题,我们应该关注其是不是一个ICG(组合博弈问题),能否使用sg函数找出其规律并求解,当你找出了sg函数的时候,你就已经可以解决这个问题了
3 大部分博弈问题的模型都或多或少与石子博弈问题有关,本题同样,输出方案的过程就是石子博弈问题输出方案的翻版
本题的SG函数模型,我们可以这样来构造,即
一个节点的Sg函数值,就是mex(sg[son])
那么判断先手必胜的条件是什么呢?,
1 我们首先想到博弈问题有关的树博弈问题,根节点的sg值就是整个游戏的sg值,这样想,样例就不对
2 将所有节点的值Sg函数值都异或起来,看起来挺有效的,但是也不太对
3 我们思考如果只有两个点的情况,如果没有边,那么就是石子博弈问题,直接取sumxor(h(x)) 就ok了,对于这一题,我们同样需要异或,就是将所有相同sg值的点的h值异或,如果有一个异或值部位0,那么肯定必赢
怎么输出方案呢?
首先我们参考有关问题的方案输出,那就是我们找到最大的sg函数相同的异或不为0的位置,如果 xorsum[sg[u]] ^ h[i] < h[i] ,说明这个时候就是可行的,它的子节点包含所有的sg值,可以同时将它的子节点的xor[sg[son]]都修改,这样最后肯定所有 的xor[sg[i]] 都为0,就是必败态
const int maxn = 2e5+10;
vector<int> G[maxn];
int topo[maxn];
int sg[maxn],deg[maxn],mem[maxn];
int Xor[maxn];
LL h[maxn];
int main(void)
{
// IOS;
int n,m;cin>>n>>m;
for(int i = 1;i <= n; ++i) scanf("%lld",&h[i]);
// cin>>h[i];
for(int i = 1;i <= m; ++i){
int u,v;
//cin>>u>>v;
scanf("%d%d",&u,&v);
G[u].Pb(v);
deg[v]++;
}
queue<int> q;
int cnt = 1;
for(int i = 1;i <= n; ++i)
if(deg[i] == 0)
q.push(i);//,vis[i] = true;
while(!q.empty()){
int p = q.front(); q.pop();
topo[cnt++] = p;
for(auto c: G[p]){
deg[c]--;
if(deg[c] == 0)
q.push(c);
}
}
for(int i = n;i >= 1; --i){
int u = topo[i];
for(auto c: G[u])
mem[sg[c]] = i;
while(mem[sg[u]] == i)
sg[u]++;
Xor[sg[u]] ^= h[u];
}
// for(int i = 1;i <= n; ++i)
// cout<<sg[i]<<" ";
// cout<<endl;
int index = -1;
for(int i = 0;i < maxn; ++i)
if(Xor[i] != 0)
index = i;
if(index == -1)
return 0*puts("LOSE");
for(int i = 1;i <= n; ++i){
if(sg[i] == index && ((h[i] ^ Xor[index]) < h[i])){
h[i] ^= Xor[index];
for(auto c: G[i]){
h[c] ^= Xor[sg[c]];
Xor[sg[c]] = 0;
}
break;
}
}
// for
puts("WIN");
for(int i = 1;i <= n; ++i)
printf("%lld ",h[i]);
return 0;
}