IOI2021集训队作业 289JE Epic Win
你和另一个人在玩石头剪刀布,双方的出招策略都按照各自的有限状态自动机进行(下一步对方通过这一步我方出什么决定到达哪一个状态)。
现在给出对方的自动机,但是你不知道对方的初始状态。你要造一个自动机使得对于任意对方的初始状态,\(10^9\)场中你的胜率为\(99\%\)。
自己的初始状态是你自己钦定的。
\(n\le 100\)
自己的自动机的状态数不超过\(50000\)。
好玩的模型。
首先胜率如此之高,基本可以确定:构造出一个方案使得失败的次数为有限次。
大概方针是:通过有限次步数来确定对方在哪个状态,后面照着对方的自动机虐爆对方。
设对方可能的状态集合为\(S\),在当前步中,你要决定出什么,对方同时出,下一局开始的时候你就知道对方在这一局出了什么。下一局开始时根据对方这一局出什么对\(S\)进行分裂,分成三个集合继续进行确认。
现在我们的目的是让\(S\)尽可能分裂。
预处理\(f_{x,y}\)表示,如果要分辨\(x,y\),那么这一步需要选什么。转移:\(f_{to_{x,c},to_{y,c}}\to f_{x,y}\)。这个转移是有后效性的,所以通过宽搜的方式转移。这样就可以\(O(n^2)\)的时间复杂度预处理。
根据类似UNR#4 同构判定鸭的结论得,似乎只需要转移\(2n\)(还是\(n\)?都过了)层(至今不会证明)。所以只需要\(O(n)\)的时间就可以区分两个状态。
于是在当前局面中,如果\(S\)中存在不同构,那么按照\(f\)的指示来走;如果同构就顺着对方的自动机完胜。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 105
#define M 50005
int n;
int s[N],to[N][3];
int f[N][N];
int q[N];
int cnt,as[M],ato[M][3];
void dfs(int[],int,int);
void divide(int q[],int k,int id){
static int q_[3][N];
int k_[3];
memset(k_,0,sizeof k_);
for (int i=0;i<k;++i)
q_[s[q[i]]][k_[s[q[i]]]++]=to[q[i]][as[id]];
memcpy(q,q_[0],sizeof(int)*k_[0]);
memcpy(q+k_[0],q_[1],sizeof(int)*k_[1]);
memcpy(q+k_[0]+k_[1],q_[2],sizeof(int)*k_[2]);
if (k_[0])
dfs(q,k_[0],ato[id][0]=++cnt);
else ato[id][0]=1;
if (k_[1])
dfs(q+k_[0],k_[1],ato[id][1]=++cnt);
else ato[id][1]=1;
if (k_[2])
dfs(q+k_[0]+k_[1],k_[2],ato[id][2]=++cnt);
else ato[id][2]=1;
}
int vis[N],BZ,p[N];
void build(int x,int id){
// printf("x=%d id=%d\n",x,id);
vis[x]=BZ,p[x]=id;
as[id]=(s[x]+1)%3;
x=to[x][as[id]];
if (vis[x]==BZ)
ato[id][0]=ato[id][1]=ato[id][2]=p[x];
else{
ato[id][0]=ato[id][1]=ato[id][2]=++cnt;
build(x,cnt);
}
}
void dfs(int q[],int k,int id){
// printf("%d %d %d\n",k,q[0],q0[1]);
for (int i=1;i<k;++i)
if (f[q[0]][q[i]]!=-1){
int c=f[q[0]][q[i]];
as[id]=c;
divide(q,k,id);
return;
}
++BZ;
build(q[0],id);
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d",&n);
char str[2];
for (int i=1;i<=n;++i){
scanf("%s%d%d%d",str,&to[i][0],&to[i][1],&to[i][2]);
s[i]=(*str=='R'?0:*str=='P'?1:2);
}
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
if (s[i]==s[j])
f[i][j]=-1;
for (int k=1;k<=n;++k)
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
if (f[i][j]==-1){
for (int c=0;c<3;++c)
if (f[to[i][c]][to[j][c]]!=-1){
f[i][j]=c;
break;
}
}
for (int i=0;i<n;++i)
q[i]=i+1;
dfs(q,n,++cnt);
printf("%d\n",cnt);
for (int i=1;i<=cnt;++i)
printf("%c %d %d %d\n",as[i]==0?'R':as[i]==1?'P':'S',ato[i][0],ato[i][1],ato[i][2]);
return 0;
}