【NOI2011T6】兔兔与蛋蛋的游戏-二分图最大匹配+博弈
测试地址:兔兔与蛋蛋的游戏
做法:这题真是好题…虽然放到考场上不一定会做…
这一道题需要用到二分图最大匹配+博弈(又是一道披着博弈皮的二分图题)。
要完成这一题,我们必须证出两个重要的结论。我们把“将棋子移入空格”看做“将空格移到棋子的位置”,然后我们就开始证明。
结论1:空格所走过的路径不会经过同一个点两次。
证明:反证法,如果路径经过同一个点两次,就说明路径上有环,由于是在矩形棋盘上走,显然环必然是偶数条边组成的,不妨设空格第一次到达该点时,下一步换入的棋子为白色,那么奇数步换入白色的棋子,偶数步应该换入黑色的棋子,而第二次到达该点的前一步是偶数步,而前面换到这一点的棋子是白色,矛盾,因而结论得证。
证明了这一结论之后,我们就可以找出所有可以走到的点,显然,空格周围的白色棋子可以走到,而这些白色棋子周围的黑色棋子也可以走到……以此类推,可以用一次BFS找出所有的可行点。我的代码里是用奇偶性判断的,这个看看就行,还是BFS比较简单吧。这样一来,问题就转化为:从一个点开始走,双方轮流行动,每次移动一格,同一个点不能经过两次,不能走的就失败,问先手必胜还是必败。
我们把可行点找出来之后,再对棋盘黑白染色,棋盘就可以转化为一个二分图,这时候我们就证明第二个结论:
结论2:如果一个点一定被二分图的最大匹配覆盖,那么从这个起点开始走先手必胜,否则先手必败。
证明:在这种情况下,先手每次走匹配边,而后手只能走非匹配边,那么后手必败。而如果起点为非匹配点,若先手某一步走到了一个非匹配点,那么从起点到这一点的路径就是增广路,和最大匹配的前提矛盾,那么先手一定会走到一个匹配点,所以后手只需走匹配边即可,这样的话先手就必败了。综上所述,结论得证。
那么我们就可以判断从某个点开始是不是先手必胜的了,步骤如下:从这个点开始BFS,从这一侧走到另一侧只走匹配边,而从另一侧走回这一侧只走非匹配边,如果走到这一侧的某个点时,这个点不在匹配中,那么就判断原来那个点先手必败。如果直到退出都没找到这样一个点,则先手必胜。算法正确性证明如下:按照这种方法找到一个点,那么BFS时所经过的从起点到该点的路径是匹配边-非匹配边交错的,且匹配边和非匹配边数量相同,交换匹配边和非匹配边,即可得到不包含起点的一个最大匹配。
那么我们只需按照操作顺序,检查从某个点出发是否先手必胜即可,因为过程中有些点不能再走了,所以就把这些点删掉(也就是打个标记)再做。但是我们没有必要每次都做一次二分图最大匹配,因为每次最多删掉一个点,我们只需对删掉的点的匹配点再找一条增广路即可。设点数为
犯二的地方:搜索的时候忘记不能走已经删掉的点了,而且每次找增广路时要清空一个标记数组,以后要注意。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,k,p,sx,sy,ans=0,anst[1010]={0},q[1610],h,t;
int tot=0,first[1610]={0},mat[1610];
struct edge {int v,next;} e[7010];
char s[50][50];
bool forb[1610]={0},vis[1610]={0};
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;
}
void init()
{
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]=='.')
{
sx=i,sy=j;
p=(sx+sy)%2;
break;
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if (s[i][j]=='.') s[i][j]='X';
if (s[i][j]=='O'&&(i+j)%2==p) continue;
if (s[i][j]=='X'&&(i+j)%2!=p) continue;
if (i>0&&s[i-1][j]=='O'&&(i+j-1)%2!=p)
insert((i-1)*m+j,i*m+j);
if (i>0&&s[i-1][j]=='X'&&(i+j-1)%2==p)
insert((i-1)*m+j,i*m+j);
if (j>0&&s[i][j-1]=='O'&&(i+j-1)%2!=p)
insert(i*m+j-1,i*m+j);
if (j>0&&s[i][j-1]=='X'&&(i+j-1)%2==p)
insert(i*m+j-1,i*m+j);
}
}
}
bool find_match(int v)
{
if (vis[v]||forb[v]) return 0;
vis[v]=1;
for(int i=first[v];i;i=e[i].next)
{
if (vis[e[i].v]||forb[e[i].v]) continue;
if (mat[e[i].v]==-1||find_match(mat[e[i].v]))
{
mat[e[i].v]=v;
mat[v]=e[i].v;
return 1;
}
}
return 0;
}
void hungary()
{
memset(mat,-1,sizeof(mat));
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if (s[i][j]=='O'&&(i+j)%2!=p&&mat[i*m+j]==-1)
{
memset(vis,0,sizeof(vis));
find_match(i*m+j);
}
}
bool check(int v)
{
memset(vis,0,sizeof(vis));
h=1,t=1;
q[1]=v;
vis[v]=1;
while(h<=t)
{
int x=q[h];
if (mat[x]==-1) return 0;
for(int i=first[mat[x]];i;i=e[i].next)
if (!vis[e[i].v]&&!forb[e[i].v])
{
vis[e[i].v]=1;
q[++t]=e[i].v;
}
h++;
}
return 1;
}
void forbid(int v)
{
forb[v]=1;
if (mat[v]!=-1)
{
mat[mat[v]]=-1;
memset(vis,0,sizeof(vis));
find_match(mat[v]);
mat[v]=-1;
}
}
void work()
{
hungary();
scanf("%d",&k);
for(int i=1;i<=k;i++)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x1--,y1--,x2--,y2--;
bool f=check(sx*m+sy);
forbid(sx*m+sy);
if (f&&check(x1*m+y1)) anst[++ans]=i;
forbid(x1*m+y1);
sx=x2,sy=y2;
}
printf("%d\n",ans);
for(int i=1;i<=ans;i++)
printf("%d\n",anst[i]);
}
int main()
{
init();
work();
return 0;
}