本题标签:博弈&二分图
建议在做本题之前先看一看
如果是大佬当我没说 好的进入正题
题目大意:
小AA和小YY玩游戏,在这个游戏中,同一个格子不能进入两次,且不能将棋子移动到某些格子中去。当玩家无法继续移动棋子时,游戏结束,最后一个移动棋子的玩家赢得了游戏。
我们发现这题和[LGOJ]P4136 谁能赢呢是非常相似的,不过添加了障碍物并且不固定起点,这么一个小小的变化让一个橙题->紫题。
在P4136中,只需判断奇偶即可,但是,换种思路,因为一个人移了石头(棋子),另一人只能移相邻的,那么就成为了一个 1 ∗ 2 1*2 1∗2的方块覆盖问题,于是便联想到这一题:ACWING372. 棋盘覆盖,不难想到,用二分图匹配求解
说回这一题,既然用二分图匹配,这里我们考虑一种经典的做法:黑白染色因为每一次走都是从黑到白或者从白到黑,所以需要建立二分图,我们对于每一个点,除去障碍,它能走到的点都连一条边
接下来我们来看这个二分图是否为完全匹配:
RT–这是一种完全匹配方案,即每一个点都落在一条边上
橙色的为一种二分图匹配方案,当然连接的边肯定不止这一些,不过我们只描绘出一种二分图最大匹配方案即可,此时我们发现只要先手不论取哪一个点,后手都一定可以接着先手的那一步取二分图中对应的点,那么就很显然了,后手必胜。
接下来,我们考虑不完全匹配方案
说明:非必须点即为不是所有最大匹配都需要的点,删掉它最大匹配数不变
看图:
这个图与上一个完全匹配的图相比多了一个节点7,这个7点就是一个非必须点此时不难想到,先手只需取到这个非必须节点,则问题就变为了上面的一个完全匹配的问题,只不过后手变成了先手。
可能有人会问,这个时候如果后手走到了另外一个非必须点呢? 答案是:不可能,因为如果又走向了一个非必须点,则此时必然构成一个新的匹配
但是这里面有一个漏洞:
在这里我省略了所有的其他边,只剩两种最大匹配方案(橙&蓝),此时若先手考虑二分图最大匹配方案蓝,则他会取取4这一非必须点,此时后手便可考虑最大匹配方案橙,跟着取3点,形成橙色匹配方案,此时经过模拟,我们发现后手赢了!那么我们需要做什么呢–找到所有最大匹配都非必须的点
这个时候,我们的任务变成了找到所有最大匹配都非必须的点,不难想到可以每次找一个非必须点并删除再继续跑二分图最大匹配,这样子显然是不可取的,那么我们应该怎么做呢?----我们发现找一个非必须点开始跑,若还能跑回这一边,则跑到的那个点也是非必须点,所以寻找步骤总结如下:
- 首先跑一下最大匹配,标记这些点
- 没有标记的点一定是非必须点,设为点集 S S S
- 从 S S S中的点出发跑dfs,只是跟 i i i落在同一侧的点也是非必须点,因为这意味着从 i i i能到对面某个点 j j j,而j能返回 i i i的同侧点 k k k,说明用 i − > j i->j i−>j可以替换掉原来的最大匹配中的边,所以 k k k也不是必须点
好的,具体就是这么多,总结:
- 如果是完全匹配,那么先手必输
- 如果不是完全匹配,那么先手挑一个非最大匹配的点落子,那么后手就会率先进入最大匹配的点,之后,先手就只用按照最打匹配的关系取选择落子,直到最大匹配走完,后手必输
- 需要找到所有最大匹配都非必须的点
讲解就到这里啦,接下来:
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
const int maxm=1005;
int n,m,tot,num;
int f[maxm][maxm],head[maxn],match[maxn],vis[maxn],color[maxn],unec[maxn];
char chess[maxm][maxm];
//num表示可放的点的个数,unec表示不必须的点
struct node
{
int from,to,next;
}edge[maxn];
inline int add(int x,int y)
{
edge[++tot].next=head[x];
edge[tot].from=x;
edge[tot].to=y;
head[x]=tot;
}
inline int read()
{
int s=0,f=1;
char c=getchar();
while (c<'0'||c>'9')
{
if (c=='-')
{
f=-1;
}
c=getchar();
}
while (c>='0'&&c<='9')
{
s=s*10+c-48;
c=getchar();
}
return s*f;
}
inline bool dfs(int x) //二分图的最大匹配
{
for (int i=head[x];i;i=edge[i].next)
{
int y=edge[i].to;
if (vis[y]==false)
{
vis[y]=true;
if (match[y]==-1||dfs(match[y])==true)
{
match[y]=x;
return true;
}
}
}
}
inline void find(int x) //寻找不必须的点
{
if (unec[x])
{
return ;
}
unec[x]=1;
for (int i=head[x];i;i=edge[i].next)
{
int y=edge[i].to;
if (match[y]!=-1)
{
find(match[y]);
}
}
}
int main()
{
n=read(),m=read();
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
cin>>chess[i][j];
}
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
if (chess[i][j]=='.')
{
f[i][j]=++num;
if ((i+j)%2==0)
{
color[f[i][j]]=1; //染色
}
}
}
}
for (int i=1;i<=n;i++) //建图
{
for (int j=1;j<=m;j++)
{
if (chess[i][j]=='.')
{
int x1=i,y1=j+1,x2=i+1,y2=j;
if (f[x1][y1])
{
add(f[i][j],f[x1][y1]);
add(f[x1][y1],f[i][j]);
}
if (f[x2][y2])
{
add(f[i][j],f[x2][y2]);
add(f[x2][y2],f[i][j]);
}
}
}
}
memset(match,-1,sizeof(match));
int ans=0;
for (int i=1;i<=num;i++) //匈牙利
{
if (color[i])
{
memset(vis,0,sizeof(vis));
ans+=dfs(i);
}
}
tot+=ans;
if (tot*2==num) //完全匹配
{
cout<<"LOSE"<<endl;
return 0;
}
cout<<"WIN"<<endl;
for (int i=1;i<=num;i++)
{
if (match[i]!=-1)
{
match[match[i]]=i;
}
}
for (int i=1;i<=num;i++)
{
if (match[i]==-1)
{
find(i);
}
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
if (unec[f[i][j]])
{
cout<<i<<" "<<j<<endl;
}
}
}
return 0;
}
/*
3 3
.##
...
#.#
*/