题解:P4055 [JSOI2009] 游戏
二分图博弈模板题。
题目中给出了一张网格图,众所周知,网格图是一类二分图,所以可以先把它按照纵横坐标之和的奇偶性进行黑白染色,最终得到了一张类似国际象棋棋盘的网格。
我们假设染色规则如下:
若纵横坐标之和为偶数,染黑色;若为奇数,染白色。
将黑色格子视为左部点,白色格子视为右部点,在可到达格子间连边,这就是一张标准的二分图。
然后我们发现「一定存在于这张二分图最大匹配中的点」为必败点,下面我们并不严格地证明这个结论:
由题目样例建立的二分图如上,注意到
如果小 AA 选择
综上,最终一定是小 AA 无处可去,故必败。所以上述必败点的补集就是所有必胜点。
那么如何求这个补集(不一定存在于这张二分图最大匹配中的点)呢,我们发现,若
于是此题的过程就出来了:
- 黑白染色,建二分图。
- 匈牙利找到最大匹配。
- 若最大匹配包含的点数等于所有点数,输出
LOSE,否则输出WIN。 - 遍历所有不在最大匹配中的点作搜索求出「不一定存在于二分图最大匹配中的点」。
- 输出这些点。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+10;
const int fx[]={1,0,-1,0};
const int fy[]={0,1,0,-1};
int n,m,match[MAXN],sum;
char c[MAXN];
vector<int> e[MAXN];
bool vis[MAXN],xr[MAXN];// xr 意为 i 是否可以不为最大匹配中的点
bool dfs(int u){
for(int v:e[u]){
if(!vis[v]){
vis[v]=1;
if(!match[v]||dfs(match[v])){
match[v]=u;
match[u]=v; // 一定要带上这句,因为 dfs2 有可能遍历到左部点
return 1;
}
}
}
return 0;
}
void dfs2(int u){
vis[u]=1,xr[u]=1;
for(int v:e[u]){
if(!match[v]||vis[match[v]])continue;
dfs2(match[v]);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>c[(i-1)*m+j];
int sum=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(c[(i-1)*m+j]=='#')continue;
for(int k=0;k<4;k++){
int tx=i+fx[k],ty=j+fy[k];
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&c[(tx-1)*m+ty]=='.')
e[(i-1)*m+j].push_back((tx-1)*m+ty);
}
if(c[(i-1)*m+j]=='.')sum++; // 统计可达点数目,从而判断是否输出 LOSE
}
}
int sum2=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if((i+j)&1||c[(i-1)*m+j]=='#')continue; // 只对可达黑色点作匈牙利
memset(vis,0,sizeof vis);
sum2+=dfs((i-1)*m+j);
}
}
if(sum2<<1==sum&&!(sum&1)){ // 当总可达点数为奇数时,一定有不在最大匹配中的点
cout<<"LOSE\n";
return 0;
}
cout<<"WIN\n";
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(!match[(i-1)*m+j]&&(c[(i-1)*m+j]=='.')) // 可达的不在最大匹配中的点
dfs2((i-1)*m+j);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(xr[(i-1)*m+j])cout<<i<<' '<<j<<'\n';
return 0;
}