【NOI2011T6】兔兔与蛋蛋的游戏-二分图最大匹配+博弈

测试地址:兔兔与蛋蛋的游戏
做法:这题真是好题…虽然放到考场上不一定会做…
这一道题需要用到二分图最大匹配+博弈(又是一道披着博弈皮的二分图题)。
要完成这一题,我们必须证出两个重要的结论。我们把“将棋子移入空格”看做“将空格移到棋子的位置”,然后我们就开始证明。
结论1:空格所走过的路径不会经过同一个点两次。
证明:反证法,如果路径经过同一个点两次,就说明路径上有环,由于是在矩形棋盘上走,显然环必然是偶数条边组成的,不妨设空格第一次到达该点时,下一步换入的棋子为白色,那么奇数步换入白色的棋子,偶数步应该换入黑色的棋子,而第二次到达该点的前一步是偶数步,而前面换到这一点的棋子是白色,矛盾,因而结论得证。
证明了这一结论之后,我们就可以找出所有可以走到的点,显然,空格周围的白色棋子可以走到,而这些白色棋子周围的黑色棋子也可以走到……以此类推,可以用一次BFS找出所有的可行点。我的代码里是用奇偶性判断的,这个看看就行,还是BFS比较简单吧。这样一来,问题就转化为:从一个点开始走,双方轮流行动,每次移动一格,同一个点不能经过两次,不能走的就失败,问先手必胜还是必败。
我们把可行点找出来之后,再对棋盘黑白染色,棋盘就可以转化为一个二分图,这时候我们就证明第二个结论:
结论2:如果一个点一定被二分图的最大匹配覆盖,那么从这个起点开始走先手必胜,否则先手必败。
证明:在这种情况下,先手每次走匹配边,而后手只能走非匹配边,那么后手必败。而如果起点为非匹配点,若先手某一步走到了一个非匹配点,那么从起点到这一点的路径就是增广路,和最大匹配的前提矛盾,那么先手一定会走到一个匹配点,所以后手只需走匹配边即可,这样的话先手就必败了。综上所述,结论得证。
那么我们就可以判断从某个点开始是不是先手必胜的了,步骤如下:从这个点开始BFS,从这一侧走到另一侧只走匹配边,而从另一侧走回这一侧只走非匹配边,如果走到这一侧的某个点时,这个点不在匹配中,那么就判断原来那个点先手必败。如果直到退出都没找到这样一个点,则先手必胜。算法正确性证明如下:按照这种方法找到一个点,那么BFS时所经过的从起点到该点的路径是匹配边-非匹配边交错的,且匹配边和非匹配边数量相同,交换匹配边和非匹配边,即可得到不包含起点的一个最大匹配。
那么我们只需按照操作顺序,检查从某个点出发是否先手必胜即可,因为过程中有些点不能再走了,所以就把这些点删掉(也就是打个标记)再做。但是我们没有必要每次都做一次二分图最大匹配,因为每次最多删掉一个点,我们只需对删掉的点的匹配点再找一条增广路即可。设点数为N,总复杂度应该是O(N2+kN),可以通过这道题(本人的总时间是20ms,非常优秀了)。
犯二的地方:搜索的时候忘记不能走已经删掉的点了,而且每次找增广路时要清空一个标记数组,以后要注意。
以下是本人代码:

#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;
}
posted @ 2017-06-08 21:02  Maxwei_wzj  阅读(128)  评论(0编辑  收藏  举报