题目描述
传送门
题目大意:给出一个n*m的棋盘,有一些障碍点,先手选择一个点,然后后手可以移动到上下左右四个格子(不能是障碍点)。两人轮流移动,不能移动到已经到达过的位置,最后不能操作的人输。
题解
二分图博弈
特点:(1)博弈双方轮流进行操作
(2)博弈状态可以分成两类,分别对于匹配的X,Y集。任意合法决策使博弈状态改变
(3)任意状态不能重复到达
(4)不能操作的人输
对于这道来说,从一个点只能转移到他周围的四个点,那么我们对棋盘进行黑白染色,那么从黑点只能到达白点,从白点只能到达黑点。黑白点就可以看成不同的博弈状态。
那么如果把相邻的点用边连接,就构成了一个二分图。
对于先手必胜的点:一定是求完最大匹配后非匹配的点。
为什么?如果放到非匹配的点上,那么后手只能到达匹配点上(否则两个点能匹配,就不满足最大匹配了)
后手到达匹配点,那么先手就能走匹配边。
然后后手只能走一条不是匹配边的边,但是他到达的点一定有一条匹配边,那么先手就可以继续走。
所以最后不能操作的一定是后手。
但是对于一个二分图,可能的最大匹配不止一种,但是任意一个最大匹配中的的非匹配点都是合法的答案。
那么如何高效的统计呢?
1.从S出发,走容量不为0的边所能到达的属于X集合的点。能到达点可以分成两类:一是非匹配点(直接到达),二是经过一个非匹配点,然后走一条类似增广路所到达的匹配点(即可以将这条路上的匹配边和非匹配边翻转得到另一组解,而这条路的终点就是另一组解中的非匹配点)
2.从T出发,沿反向弧为0的边走所能到达的属于Y集合的点。能到达的点也可以分成两类,与上面S的分析方法类似。
那么二分图博弈就转换成了最大匹配问题。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define N 103
#define M 1000003
#define inf 1000000000
using namespace std;
int point[M],nxt[M],v[M],remain[M],n,m,c[N][N],last[M],cur[M],vis[M],opt[M],px[M],py[M];
int dx[10]={0,1,0,-1},dy[10]={1,0,-1,0},tot,cnt,top,st[M],deep[M],num[M],S,T;
char ch[N][N];
struct data{
int x,y;
}a[M];
void add(int x,int y,int z)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z;
tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;
}
int addflow(int s,int t)
{
int now=t; int ans=inf;
while (now!=s) {
ans=min(ans,remain[last[now]]);
now=v[last[now]^1];
}
now=t;
while (now!=s) {
remain[last[now]]-=ans;
remain[last[now]^1]+=ans;
now=v[last[now]^1];
}
return ans;
}
void bfs(int s,int t)
{
for (int i=1;i<=t;i++) deep[i]=t+1;
queue<int> p; p.push(t);
deep[t]=0;
while (!p.empty()) {
int now=p.front(); p.pop();
for (int i=point[now];i!=-1;i=nxt[i])
if (deep[v[i]]==t+1&&remain[i^1])
deep[v[i]]=deep[now]+1,p.push(v[i]);
}
}
int isap(int s,int t)
{
bfs(s,t); int now=s; int ans=0;
for (int i=1;i<=t;i++) num[deep[i]]++;
for (int i=1;i<=t;i++) cur[i]=point[i];
while (deep[s]<t) {
if (now==t) {
ans+=addflow(s,t);
now=s;
}
bool mark=false;
for (int i=point[now];i!=-1;i=nxt[i])
if (deep[v[i]]+1==deep[now]&&remain[i]){
mark=true;
cur[now]=i;
last[v[i]]=i;
now=v[i];
break;
}
if (!mark){
int minn=t+1;
for (int i=point[now];i!=-1;i=nxt[i])
if (remain[i]) minn=min(minn,deep[v[i]]);
if (!--num[deep[now]]) break;
num[deep[now]=minn+1]++;
if (now!=s) now=v[last[now]^1];
}
}
return ans;
}
void solve(int x,int t)
{
vis[x]=1;
if (opt[x]==t) st[++top]=x;
for (int i=point[x];i!=-1;i=nxt[i])
if (!vis[v[i]]&&(remain[i]==(t!=2)))
solve(v[i],t);
}
int cmp(data a,data b){
return a.x<b.x||a.x==b.x&&a.y<b.y;
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%s",ch[i]+1);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (ch[i][j]=='.') {
c[i][j]=++cnt;
px[cnt]=i; py[cnt]=j;
}
S=cnt+1; T=S+1; tot=-1;
memset(point,-1,sizeof(point));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (ch[i][j]=='.') {
if ((i^j)&1) {
add(S,c[i][j],1);
opt[c[i][j]]=1;
for (int k=0;k<4;k++) {
int tx=i+dx[k]; int ty=j+dy[k];
if (tx<1||ty<1||tx>n||ty>m||ch[tx][ty]=='#') continue;
add(c[i][j],c[tx][ty],1);
}
}else add(c[i][j],T,1),opt[c[i][j]]=2;
}
int flow=isap(S,T);
if ((flow<<1)==cnt) {
printf("LOSE\n");
return 0;
}
printf("WIN\n");
solve(S,1);
memset(vis,0,sizeof(vis));
solve(T,2);
for (int i=1;i<=top;i++) a[i].x=px[st[i]],a[i].y=py[st[i]];
sort(a+1,a+top+1,cmp);
for (int i=1;i<=top;i++) printf("%d %d\n",a[i].x,a[i].y);
}