【BZOJ1443】游戏Game(JSOI2009)-二分图最大匹配+博弈
测试地址:游戏Game
做法:这一题的思想十分巧妙……这是一道披着博弈皮的二分图匹配题啊……
这一题需要用到二分图最大匹配+博弈。
首先我们注意到,如果将矩形黑白间隔染色,那么矩形上任何一条边都连接黑白两色的点,这启发我们把矩阵转化成二分图进行处理。然后我们研究什么样的起点才能保证后手必胜(这里称先走的为先手)。
显然,如果二分图存在完备匹配,那么后手必败,因为先手只需要按着匹配边走,就可以把后手卡死,那么反之,如果二分图不存在完备匹配,那么一定存在一个起点使得后手必胜。那么这样的点有什么性质呢?先说结论:如果存在一个最大匹配不包含某点,那么这个点就是使得后手必胜的点。证明如下:先手从左侧某点走出时,可能走到匹配点或非匹配点,如果走到匹配点,那么后手就走匹配边即可,如果走到非匹配点,那么起点走到这个点的路径就是一条增广路,这显然与最大匹配的前提矛盾,那么这样一直走下去,先手一定会被卡死,综上所述,以这样的点作为起点则后手必胜。那么现在问题就变成了怎么找这样的点。
有的同学可能会想,我们做一次最大匹配,把没有覆盖到的点直接输出不就好了吗?注意我上面说的,我们要找的点是“存在一个最大匹配不包含的”,而最大匹配可能有很多,那么要怎么办呢?我们可以做两次最大匹配,每次只考虑二分图的一侧上有没有可能的起点,那么我们可以这样做:首先做一次最大匹配,然后把没被覆盖到的点放进队列,然后每个点可以扩展到另一侧的匹配点,那么这些匹配点所对应的一侧上的点就也是答案,再把这些点放进队列。为什么这样做是对的呢?注意到我们如果按照以上走法,走过的路径应该是一条类似增广路的路径,只不过非匹配边和匹配边数量相等,那么我们把非匹配边换成匹配边,把匹配边换成非匹配边,匹配数是不变的,这样变换之后还是一个最大匹配,但是这样的话走到的点就会变成非匹配点,那么和条件“存在一个最大匹配不包含”相符,所以这个点也是答案。这样我们就解决了这一问题。
做二分图最大匹配时,匈牙利算法的时间复杂度是平方级的,虽然看上去有10000个点跑不过,但是匈牙利算法常数很小,而且搭配这个图的特殊性,常数就更小了,所以时间是不用担心的,亲测可过。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,tot=0,first[10010]={0},q[10010]={0},vis[10010]={0},h,t;
int l[5010]={0},r[5010]={0},mat[10010],maxmat;
char s[110][110];
bool ans[10010]={0};
struct edge {int v,next;} e[50010];
void insert(int a,int b)
{
e[++tot].v=b,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],first[b]=tot;
}
bool find_match(int v,int p)
{
vis[v]=p;
for(int i=first[v];i;i=e[i].next)
{
if (mat[e[i].v]==-1)
{
mat[e[i].v]=v;
mat[v]=e[i].v;
return 1;
}
else if (vis[mat[e[i].v]]!=p&&find_match(mat[e[i].v],p))
{
mat[e[i].v]=v;
mat[v]=e[i].v;
return 1;
}
}
return 0;
}
void find_answer()
{
for(int i=h;i<=t;i++) vis[q[i]]=1;
while(h<=t)
{
int v=q[h];
ans[v]=1;
for(int i=first[v];i;i=e[i].next)
if (mat[e[i].v]!=-1&&!vis[mat[e[i].v]])
{
vis[mat[e[i].v]]=1;
q[++t]=mat[e[i].v];
}
h++;
}
}
void hungary(bool mode)
{
maxmat=0;
memset(mat,-1,sizeof(mat));
memset(vis,0,sizeof(vis));
h=1,t=0;
if (!mode)
{
for(int i=1;i<=l[0];i++)
if (mat[l[i]]==-1)
if (find_match(l[i],i)) maxmat++;
for(int i=1;i<=l[0];i++)
if (mat[l[i]]==-1) q[++t]=l[i];
}
else
{
for(int i=1;i<=r[0];i++)
if (mat[r[i]]==-1)
if (find_match(r[i],i)) maxmat++;
for(int i=1;i<=r[0];i++)
if (mat[r[i]]==-1) q[++t]=r[i];
}
memset(vis,0,sizeof(vis));
find_answer();
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%s",s[i]);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
if (s[i][j]=='#') continue;
if ((i+j)%2==0) l[++l[0]]=i*m+j;
else r[++r[0]]=i*m+j;
if (j>0&&s[i][j-1]=='.') insert(i*m+j-1,i*m+j);
if (i>0&&s[i-1][j]=='.') insert((i-1)*m+j,i*m+j);
}
hungary(0);
if (maxmat==l[0]&&maxmat==r[0]) {printf("LOSE");return 0;}
hungary(1);
printf("WIN\n");
for(int i=0;i<n*m;i++)
if (ans[i]) printf("%d %d\n",i/m+1,i%m+1);
return 0;
}