题解:P3838 [IOI 2017] Toy Train
博弈论好题。
题意
一个
题解
不希望一次性把所有点的出边全都枚举完。一次性考虑很多东西时,要么可以直接猜出来通性,要么分步考虑。于是我们把游戏转化为序贯的。一种转化思路就是,从起点开始,由火车所在车站的人决定火车往哪里走。这个东西感性上就是把策略展开来成一个搜索树。
但是这样子的博弈状态还是太多了,走上一个有充电器的环的还是太不好判定了。再做一次转化,把第一次进入火车站时就定死出边改成每次进入火车站时都能选择出边。胜利条件改成能无限次走到充电站。
不难发现一个点的状态只有这个点的编号了,于是每次到达一个点必然只会选择一个出边,因为不存在其他任意的信息。于是这等价于在第一次进入时定死出边,自然胜利判断也是等价的。
我们继续改良胜利条件。注意到无限有数学归纳法的等价定义:无论
于是我们可以再跑一遍有向图博弈(称为第二次有向图博弈),现在必败点是确定的。跑出来的非必败点就是答案了吗?交上去发现 WA 了。走到现在这步田地还是因为不严谨导致的。
又感觉博弈论一旦细想就会陷入各种各样的悖论。就举个例子,
下了数据发现,为了不走到必败点,一些平局点是无法到达关键点的。这些点也应该被判掉,这就需要再跑一次上面的流程。如是循环,直到图不发生变化。这样子是
我们下面严谨地证明以上的每一步。但也请注意,没有上文的尝试、猜想,很难把证明直接搞出来,这就是“先不管证明”的重要性。
我们的思路是先转化为每次进入火车站的时候都做一次决策,
再次回到一个点时,一定是走了一个环,若这个环没有充电器,说明走过去后对方的最优策略也黔驴技穷,肯定选择之前走的路;若这个环有充电器,而我们换了一条边,说明原来的选择不够优,矛盾。还有一种证明的思路是,显然不会经过无限个点到达充电站(具体地若走了
对于无限次走到充电站这个朱鹮,因为充电站是有限的,必然存在一个充电站经历了无数次,自然是找到含有充电站的环了。于是这个转化是严谨的。
然后把可以无限次到达充电站转化为:无论
问题出在所跑的有向图博弈和原问题不等价,不走到必败点不意味着胜利,还有可能是和(对
思考这道题时,我常常对无穷很担忧,但是这道题中大多数的无穷都可以转化为
事实上我们不用关心初始化第一次有向图博弈时,本身是充电站算不算,只要本身不是必败点即可。
Code:
#include<bits/stdc++.h>
using namespace std;
//#define int long long//乱开ll见祖宗
#define pbI push_back
#define rep(a,b,c) for(signed a=b;a<=c;a++)
#define MAXN 20010
int n,ans[MAXN],deg[MAXN],lst[MAXN],deg0[MAXN];
bool bel[MAXN],ene[MAXN];
vector<int>vec[MAXN];
vector<int> who_wins(vector<int>a,vector<int>r,vector<int>u,vector<int>v){
int m=u.size();n=a.size();
rep(i,0,m-1){vec[v[i]+1].pbI(u[i]+1);deg0[u[i]+1]++;}
rep(i,0,n-1)bel[i+1]=a[i];rep(i,0,n-1)ene[i+1]=r[i];
while(1){
memcpy(lst,ans,sizeof(ans));
memcpy(deg,deg0,sizeof(deg));
queue<int>q;
rep(i,1,n)if(ene[i]&&ans[i]!=-1)q.push(i),ans[i]=1;
while(q.size()){
int u=q.front();q.pop();
for(auto v:vec[u]){
deg[v]--;if(ans[v])continue;
if(bel[v]&&(ans[u]==1))ans[v]=1,q.push(v);
else if(!bel[v]&&(ans[u]==-1))ans[v]=-1,q.push(v);
else if(!deg[v]){ans[v]=bel[v]?-1:1;q.push(v);}
}
}
memcpy(deg,deg0,sizeof(deg));
rep(i,1,n)if(ans[i]!=1)ans[i]=-1,q.push(i);
while(q.size()){
int u=q.front();q.pop();
for(auto v:vec[u]){
deg[v]--;if(ans[v]==-1)continue;
if(!bel[v]&&(ans[u]==-1))ans[v]=-1,q.push(v);
else if(bel[v]&&(ans[u]==1))ans[v]=1,q.push(v);
else if(!deg[v]){ans[v]=bel[v]?-1:1;q.push(v);}
}
}
bool can=0;
rep(i,1,n)if(lst[i]!=-1&&ans[i]==-1)can=1;
rep(i,1,n)if(ans[i]!=-1)ans[i]=0;
if(!can)break;
}
vector<int>ret;
rep(i,1,n)ret.pbI(ans[i]!=-1);
return ret;
}
这一版代码里面泾渭分明地区分了第一次和第二次有向图博弈。应该不难理解了吧。