- 给定一张\(n\times m\)的棋盘,上面有一些障碍格。
- 选择一个非障碍格放一个棋子,先手后手轮流将它移到一个相邻的没有到过的非障碍格,无法移动的人输。
- 求出所有满足先手必输的非障碍格。
- \(n,m\le100\)
挺久以前做过这道题的加强版:【BZOJ2437】[NOI2011] 兔兔与蛋蛋。
当时觉得好神仙,怎么想到二分图的(尽管现在依然这么想),如今才知道原来这一切都是套路。
二分图博弈
首先发现如果我们给这张棋盘黑白染色,然后在所有相邻的非障碍格之间连边,建出来的应该是一张二分图。
然后就有一个结论,一个点是必胜点,当且仅当去掉它之后二分图最大匹配数量会减少(显然减少的话只会减\(1\))。
假设在除去当前点后仍存在一个最大匹配,那我们从当前点必然只能沿非匹配边(如果没有,就说明输了)走向另一端的一个点,而另一端的点必然能够通过一条匹配边(必然存在,否则就不是最大匹配了)走回来。
也就是说,从这边不一定能找到一条边过去,但从那边却一定能找到一条边回来,显然某一刻无法再过去,那么就输了。
反之,若除去当前点后不存在最大匹配了,按照类似的证明步骤,便可得当前决胜者存在必胜策略。
而要判断去掉一个点之后最大匹配是否会减少,就是要看原本与它匹配的点能否找到一个新的匹配点,直接匈牙利算法即可。
代码:\(O(n^2m^2)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define P(x,y) (((x)-1)*m+(y))
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,ee,lnk[N*N+5],sx[N*N+5],sy[N*N+5];
struct edge {int to,nxt;}e[4*N*N+5];char s[N+5][N+5];
namespace H//匈牙利算法
{
int vis[N*N+5],s[N*N+5],del[N*N+5];I bool Match(CI x,CI w,CI ti)//w表示当前删去的点
{
for(RI i=lnk[x];i;i=e[i].nxt)
{
if(vis[e[i].to]==ti||e[i].to==w) continue;vis[e[i].to]=ti;
if(!s[e[i].to]||Match(s[e[i].to],w,ti)) return s[s[e[i].to]=x]=e[i].to;
}return 0;
}
}
int main()
{
RI i,j,ti=0;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",s[i]+1);
for(i=1;i<=n;++i) for(j=1;j<=m;++j)//建二分图
s[i][j]=='.'&&s[i][j+1]=='.'&&(add(P(i,j),P(i,j+1)),add(P(i,j+1),P(i,j))),
s[i][j]=='.'&&s[i+1][j]=='.'&&(add(P(i,j),P(i+1,j)),add(P(i+1,j),P(i,j)));
for(i=1;i<=n;++i) for(j=1;j<=m;++j) s[i][j]^'#'&&!H::s[P(i,j)]&&H::Match(P(i,j),0,++ti);//求出初始匹配
RI x,y,t=0;for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(s[i][j]^'#') x=P(i,j),y=H::s[x],//枚举每个点判断是否为必输点
(!y||(H::s[x]=H::s[y]=0,H::Match(y,x,++ti)))?(sx[++t]=i,sy[t]=j):H::Match(x,0,++ti);//本来就不在最大匹配中,或去掉之后最大匹配数不变
for(printf("%d\n",t),i=1;i<=t;++i) printf("%d %d\n",sx[i],sy[i]);return 0;//输出答案
}