test20200415 游戏
Alice和Bob在玩一个游戏,给出一张(n imes m)的棋盘,上面有一些点是障碍,游戏开始时,Alice选定棋盘上任意一个不是障碍的格子,并且将一枚棋子放在其中,然后Bob先手,两人轮流操作棋子,每次操作必须将棋子从当前位置移动到一个相邻的无障碍且未经过的格子(即每个格子不允许经过两次),不能操作的人输,如果两人都按照最优策略操作,请问初始时Alice将棋子放在哪些格子上有必胜策略。
对于100%的数据:(1leq n,mleq 100)。
题解
首先这是一张二分图,我们分别考虑每个联通块。
-
如果一个点不一定在最大匹配中,那么这个点Alice必胜。
证明:任选一个这个点未匹配的最大匹配。Bob第一次走的一定非匹配边,之后Alice只要一直走匹配边,Bob就只能走非匹配边。如果Bob获胜说明找到了一条增广路,矛盾。
-
如果一个点一定在最大匹配中,那么这个点Bob必胜。
证明:任选一个最大匹配,Bob只要一直走匹配边,Alice就只能走非匹配边。如果Alice获胜就可以构造一个这个点未匹配的最大匹配,矛盾。
所以只需要判断每个点是否不一定在最大匹配里即可。用类似找增广路的方式随便找一找就行了。
时间复杂度(O(nmsqrt{nm}))。
CO int N=101;
char grid[N][N];
int idx[N][N],num;
int pos[N*N][2],col[N*N];
vector<int> to[N*N];
int vis[N*N],tim,mat[N*N];
bool dfs(int x){
for(int y:to[x])if(vis[y]!=tim){
vis[y]=tim;
if(!mat[y] or dfs(mat[y])){
mat[y]=x;
return 1;
}
}
return 0;
}
int win[N*N];
void find(int x){
for(int y:to[x]){
if(mat[y]!=0 and win[mat[y]]==0)
win[mat[y]]=1,find(mat[y]);
}
}
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) scanf("%s",grid[i]+1);
function<void(int,int)> link=[&](int u,int v)->void{
to[u].push_back(v),to[v].push_back(u);
};
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)if(grid[i][j]=='.'){
idx[i][j]=++num;
pos[num][0]=i,pos[num][1]=j;
col[num]=(i+j)&1;
if(i>1 and grid[i-1][j]=='.') link(num,idx[i-1][j]);
if(j>1 and grid[i][j-1]=='.') link(num,idx[i][j-1]);
}
for(int i=1;i<=num;++i)if(!col[i])
++tim,dfs(i);
for(int i=1;i<=num;++i)if(col[i])
if(mat[i]) mat[mat[i]]=i;
for(int i=1;i<=num;++i)if(!mat[i])
win[i]=1,find(i);
int ans=accumulate(win+1,win+num+1,0);
printf("%d
",ans);
for(int i=1;i<=num;++i)if(win[i])
printf("%d %d
",pos[i][0],pos[i][1]);
return 0;
}
NOI2011 兔兔与蛋蛋游戏
这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。
这个游戏是在一个 (n) 行 (m) 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。
每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:
-
兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。
-
蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。
第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第x行第y列中的棋子移进空格中”记为 (M(x,y))。
例如下面是三个游戏的例子。
最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。
注意:
-
两个格子相邻当且仅当它们有一条公共边。
-
兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。
对于 (100\%) 的数据,(1leq nleq 40),(1 leq mleq 40),(1leq kleq 1000)。
题解
https://www.luogu.com.cn/blog/duyi/solution-p1971
我们把移动的过程看做是中间空格在走,则空格一定是在黑格子与白格子间交替移动,这就变成了一个二分图的模型:
在一个二分图中,从给定的起点u开始移动棋子,两个玩家轮流移动,不得经过重复的点,若一方无法移动即为输家。
这个模型的结论是:后手必胜(先手必败)当且仅当存在最大匹配不包含起点u。
简单理解一下,当存在一组最大匹配不包含起点u时,先手从u出发,先随便走一条非匹配边,此时后手一定往下走一条匹配边,先手再走一条非匹配边,如此往复直到最后先手无边可走。此时后手必胜。为什么后手总能找到下一条匹配边?因为否则它们走出的路径就由非匹配边开头,非匹配边结束——这是一条增广路!显然不符合“最大匹配”这一条件。
如何检验是否存在不包含u的最大匹配呢?我们只需要把u去掉,看原图还能否增广,若能,说明找到了新的最大匹配,即存在一种不包含u的最大匹配,此时先手必败,反之先手必胜。
注意这题实现细节:一个空格被挪走后,它原来所在的格子应该被ban掉。因为原来所在的格子的颜色前后发生了本质变化,所以它连的边都没了。
CO int N=50,M=1e3+10;
int grid[N][N],idx[N][N],num;
vector<int> to[N*N];
int vis[N*N],tim,mat[N*N],ban[N*N];
CO int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
bool dfs(int x){
if(ban[x]) return 0;
for(int y:to[x]){
if(vis[y]==tim or ban[y]) continue;
vis[y]=tim;
if(!mat[y] or dfs(mat[y])){
mat[x]=y,mat[y]=x;
return 1;
}
}
return 0;
}
int main(){
int n=read<int>(),m=read<int>();
int bx=0,by=0;
for(int i=1;i<=n;++i){
static char str[N];scanf("%s",str+1);
for(int j=1;j<=m;++j){
if(str[j]=='O') grid[i][j]=0;
else if(str[j]=='X') grid[i][j]=1;
else grid[i][j]=1,bx=i,by=j;
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) idx[i][j]=++num;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)if(grid[i][j]){
for(int k=0;k<4;++k){
int x=i+dx[k],y=j+dy[k];
if(x<1 or x>n or y<1 or y>m) continue;
if(grid[x][y]) continue;
to[idx[i][j]].push_back(idx[x][y]);
to[idx[x][y]].push_back(idx[i][j]);
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)if(grid[i][j])
++tim,dfs(idx[i][j]);
int q=read<int>();
vector<int> ans;
for(int i=1;i<=q;++i){
int win=0;
int x=idx[bx][by];
ban[x]=1;
if(mat[x]){
int y=mat[x];
mat[x]=mat[y]=0;
++tim,win+=!dfs(y);
}
read(bx),read(by);
x=idx[bx][by];
ban[x]=1;
if(mat[x]){
int y=mat[x];
mat[x]=mat[y]=0;
++tim,win+=!dfs(y);
}
read(bx),read(by);
if(win==2) ans.push_back(i);
}
printf("%zd
",ans.size());
for(int x:ans) printf("%d
",x);
return 0;
}