NOI2011 兔兔与蛋蛋游戏

http://www.lydsy.com/JudgeOnline/problem.php?id=2437

这道题真是极好的。

75分做法:

搜索。

出题人真的挺良心的,前15个数据点的范围都很小,可以直接搜索。

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<utility>
#include<set>
#include<bitset>
#include<vector>
#include<functional>
#include<deque>
#include<cctype>
#include<climits>
#include<complex>
//#include<bits/stdc++.h>适用于CF,UOJ,但不适用于poj

using namespace std;

typedef long long LL;
typedef double DB;
typedef pair<int,int> PII;
typedef complex<DB> CP;

#define mmst(a,v) memset(a,v,sizeof(a))
#define mmcy(a,b) memcpy(a,b,sizeof(a))
#define re(i,a,b)  for(i=a;i<=b;i++)
#define red(i,a,b) for(i=a;i>=b;i--)
#define fi first
#define se second
#define m_p(a,b) make_pair(a,b)
#define SF scanf
#define PF printf
#define two(k) (1<<(k))

template<class T>inline T sqr(T x){return x*x;}
template<class T>inline void upmin(T &t,T tmp){if(t>tmp)t=tmp;}
template<class T>inline void upmax(T &t,T tmp){if(t<tmp)t=tmp;}

const DB EPS=1e-9;
inline int sgn(DB x){if(abs(x)<EPS)return 0;return(x>0)?1:-1;}
const DB Pi=acos(-1.0);

inline int gint()
  {
        int res=0;bool neg=0;char z;
        for(z=getchar();z!=EOF && z!=‘-‘ && !isdigit(z);z=getchar());
        if(z==EOF)return 0;
        if(z==‘-‘){neg=1;z=getchar();}
        for(;z!=EOF && isdigit(z);res=res*10+z-‘0‘,z=getchar());
        return (neg)?-res:res;
    }
inline LL gll()
  {
      LL res=0;bool neg=0;char z;
        for(z=getchar();z!=EOF && z!=‘-‘ && !isdigit(z);z=getchar());
        if(z==EOF)return 0;
        if(z==‘-‘){neg=1;z=getchar();}
        for(;z!=EOF && isdigit(z);res=res*10+z-‘0‘,z=getchar());
        return (neg)?-res:res;
    }

const int maxN=40;
const int dx[]={0,0,-1,1};
const int dy[]={1,-1,0,0};
const int maxK=1000;

int N,M,K;
int mp[maxN+10][maxN+10];
int x,y;

int tot,win[maxK+100];

inline int find(int x,int y,int z)
  {
      int i;
      re(i,0,3)
        {
            int tx=x+dx[i],ty=y+dy[i];
            if(1<=tx && tx<=N && 1<=ty && ty<=M && mp[tx][ty]==z)
              {
                  swap(mp[x][y],mp[tx][ty]);
                  if(!find(tx,ty,((z-1)^1)+1)){swap(mp[x][y],mp[tx][ty]);return 1;}
                  swap(mp[x][y],mp[tx][ty]);
              }
        }
      return 0;
  }

int main()
  {
      freopen("game.in","r",stdin);
      freopen("game.out","w",stdout);
      int i,j;
      N=gint();M=gint();
      re(i,1,N)re(j,1,M)
        {
            char z=getchar();while(z!=‘.‘ && z!=‘O‘ && z!=‘X‘)z=getchar();
            switch(z)
              {
                  case ‘O‘:mp[i][j]=1;break;
                  case ‘X‘:mp[i][j]=2;break;
                  case ‘.‘:mp[i][j]=0;x=i;y=j;break;
              }
        }
      K=gint();
      re(i,1,K)
        {
            int tx=gint(),ty=gint();
            win[i]=find(x,y,1);
            swap(mp[x][y],mp[tx][ty]);
            x=tx;y=ty;
            if(win[i] && find(x,y,2)){tot++;win[i]=1;}else win[i]=0;
            tx=gint(),ty=gint();
            swap(mp[x][y],mp[tx][ty]);
            x=tx;y=ty;
        }
      PF("%d\n",tot);
      re(i,1,K)if(win[i])PF("%d\n",i);
      return 0;
  }

100分做法:

二分图匹配。

性质1 空格移动的路径一定不会自交。

记出发格子为A_0,第i步到达的格子为A_i。

虽然第一次相交的点不一定是A_0,但不失一般性,假设走了n步之后第一次与A_0相交,即走过了A_0,A_1,A_2,...,A_n-1,A_n。

因为每次是移动是上下左右四个方向之一,因为又回到出发点,所以有多少次向上走就有多少次向下走,有多少次向左走就有多少次向右走,所以n是偶数。

我们发现,第奇数次移动的为先手,即A_1,A_3,A_5,...,A_n-1;第偶数次移动的为后手,即A_0,A_2,A_4,...,A_n。

因为又回到了出发地,所以A_1和A_n是同一个棋子,但是2个人同时移动了这个棋子,矛盾,所以空格移动的路径一定不会自交。

不妨将刚开始时空格所在的格子看成黑色 那么空格移动的路径一定是黑白相间的。

建立二分图,左边为黑色,右边为白色,之间有相邻关系的连边。兔兔是从左边走到右边,蛋蛋是从右边走到左边。

性质2 当且仅当最大匹配一定覆盖空格所在的结点时,兔兔必胜;否则蛋蛋必胜。

(1)如果存在一个最大匹配不覆盖空格所在的结点,蛋蛋必胜。

如图实线是匹配边,虚线是非匹配边,空格所在的结点为start。

因为最大匹配不覆盖空格所在的结点start,所以兔兔只能沿着某一条非匹配边到右边,不妨设到了v(如果没有到右边的没走过的非匹配边,那么兔兔输了)。

v一定是被覆盖的(不然start就可以连到v,就不是最大匹配了)。

蛋蛋可以沿着覆盖v的匹配边到左边的u。

也就是说,当兔兔到了右边后,蛋蛋一定有路径回到左边;但是当蛋蛋到了左边后,兔兔不一定有路径到右边。

所以如果存在一个最大匹配不覆盖空格所在的结点,蛋蛋必胜。

(2)如果最大匹配一定覆盖空格所在的结点时,兔兔必胜。

我们可以类似(1)中进行分析。

虽然这道题不是问我们谁必胜,但这给我们接下来提供了一种思考方法。

现在兔兔走第1步,从start走到v。

首先我们根据性质2,判断兔兔是否必胜,就是判断使用start点和不使用start点时的最大匹配是否相等,如果不相等,说明最大匹配一定覆盖start点,兔兔必胜。

然后强行覆盖start到v的边。

我们要这时候蛋蛋要从左边往右边走,我们要判断蛋蛋是否必胜。

如果蛋蛋能够走到兔兔的一个必败态,那么蛋蛋必胜。

根据性质2,我们得出结论:在start到v的边一定被覆盖的情况下,当且仅当与v有边相连的所有点都一定被最大匹配覆盖,蛋蛋必输;否则蛋蛋必胜。

所以如果在某种最大匹配方案中,与v相连的某个点没有被最大匹配覆盖,那么蛋蛋必胜。

如图,与v相连的点为a,b,c,在图示的最大匹配方案中,c没有被最大匹配覆盖,所以蛋蛋必胜。

接下来读入蛋蛋第1步走的格子,start变成为蛋蛋第1步走的格子。

然后类似做就可以了。

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<utility>
#include<set>
#include<bitset>
#include<vector>
#include<functional>
#include<deque>
#include<cctype>
#include<climits>
#include<complex>
//#include<bits/stdc++.h>适用于CF,UOJ,但不适用于poj

using namespace std;

typedef long long LL;
typedef double DB;
typedef pair<int,int> PII;
typedef complex<DB> CP;

#define mmst(a,v) memset(a,v,sizeof(a))
#define mmcy(a,b) memcpy(a,b,sizeof(a))
#define re(i,a,b)  for(i=a;i<=b;i++)
#define red(i,a,b) for(i=a;i>=b;i--)
#define fi first
#define se second
#define m_p(a,b) make_pair(a,b)
#define SF scanf
#define PF printf
#define two(k) (1<<(k))

template<class T>inline T sqr(T x){return x*x;}
template<class T>inline void upmin(T &t,T tmp){if(t>tmp)t=tmp;}
template<class T>inline void upmax(T &t,T tmp){if(t<tmp)t=tmp;}

const DB EPS=1e-9;
inline int sgn(DB x){if(abs(x)<EPS)return 0;return(x>0)?1:-1;}
const DB Pi=acos(-1.0);

inline int gint()
  {
        int res=0;bool neg=0;char z;
        for(z=getchar();z!=EOF && z!=‘-‘ && !isdigit(z);z=getchar());
        if(z==EOF)return 0;
        if(z==‘-‘){neg=1;z=getchar();}
        for(;z!=EOF && isdigit(z);res=res*10+z-‘0‘,z=getchar());
        return (neg)?-res:res;
    }
inline LL gll()
  {
      LL res=0;bool neg=0;char z;
        for(z=getchar();z!=EOF && z!=‘-‘ && !isdigit(z);z=getchar());
        if(z==EOF)return 0;
        if(z==‘-‘){neg=1;z=getchar();}
        for(;z!=EOF && isdigit(z);res=res*10+z-‘0‘,z=getchar());
        return (neg)?-res:res;
    }

const int maxN=40;
const int dx[]={0,0,-1,1};
const int dy[]={1,-1,0,0};
const int maxcnt=maxN*maxN;
const int maxK=1000;

int N,M,K;
char mp[maxN+10][maxN+10];
int idx[maxN+10][maxN+10],cntB,cntW;
int x,y;

int now,first[maxcnt+100];
struct Tedge{int v,next;}edge[maxcnt*4+100];
inline void addedge(int u,int v){now++;edge[now].v=v;edge[now].next=first[u];first[u]=now;}

int maxmatching;
int form[maxcnt+100],flag[maxcnt+100];

int vis[maxcnt+100];
inline int find(int u)
  {
      int i,v;
      vis[u]=1;
      for(i=first[u],v=edge[i].v;i!=-1;i=edge[i].next,v=edge[i].v)
        if(flag[v]==0 && (form[v]==0 || (vis[form[v]]==0 && find(form[v]))))
              {
              form[u]=v;form[v]=u;
              return 1;
          }
      return 0;
  }
inline int check(int u)
  {
      int i;
      re(i,1,cntB+cntW)vis[i]=0;
      return find(u);
  }

inline void disuse(int u)
  {
      if(form[u]==0)return;
      int v=form[u];
      form[u]=form[v]=0;
      maxmatching--;
      flag[u]=1;
      if(check(v))maxmatching++;
  }
inline void use(int u)
  {
      flag[u]=0;
        if(check(u))maxmatching++;
  }

inline void cover(int u,int v)
  {
      if(form[u]==v){flag[u]=flag[v]=1;return;}
      int f=0,g=0;
      if(form[u]!=0)g=form[u],form[g]=form[u]=0,maxmatching--;
      if(form[v]!=0)f=form[v],form[f]=form[v]=0,maxmatching--;
      form[u]=v;form[v]=u;
      flag[u]=flag[v]=1;
      maxmatching++;
      if(f && check(f))maxmatching++;
      if(g && check(g))maxmatching++;
    }

int tot,out[maxK+100];

int main()
  {
      freopen("game.in","r",stdin);
      freopen("game.out","w",stdout);
      int i,j,k;
      N=gint();M=gint();
      re(i,1,N)scanf("%s\n",mp[i]+1);
      re(i,1,N)re(j,1,M)
          {
              if(mp[i][j]==‘O‘)idx[i][j]=++cntW;else idx[i][j]=++cntB;
              if(mp[i][j]==‘.‘)mp[i][j]=‘X‘,x=i,y=j;
            }
        re(i,1,N)re(j,1,M)if(mp[i][j]==‘O‘)idx[i][j]+=cntB;
        now=-1;mmst(first,-1);
        re(i,1,N)re(j,1,M)if(mp[i][j]==‘X‘)re(k,0,3)
          {
              int x=i+dx[k],y=j+dy[k];
              if(x<1 || N<x || y<1 || M<y) continue;
              if(mp[x][y]==‘O‘)addedge(idx[i][j],idx[x][y]),addedge(idx[x][y],idx[i][j]);
          }

        re(i,1,cntB)if(check(i))maxmatching++;

        K=gint();
        re(i,1,K)
          {
              int tx=gint(),ty=gint(),u=idx[x][y],v=idx[tx][ty];
                disuse(u);
              int res1=maxmatching;
              use(u);
              int res2=maxmatching;
              cover(u,v);
              if(res1!=res2)
                  {
                      int f=0,t;
                      for(j=first[v],t=edge[j].v;j!=-1;j=edge[j].next,t=edge[j].v)
                          if(flag[t]==0 && form[t]==0){f=1;break;}
                      if(!f)
                        {
                            int res3=maxmatching;
                          for(j=first[v],t=edge[j].v;j!=-1;j=edge[j].next,t=edge[j].v)if(flag[t]==0)
                            {
                                 disuse(t);
                                int res4=maxmatching;
                                use(t);
                                if(res4==res3){f=1;break;}
                              }
                        }
                      if(f)out[++tot]=i;
                    }
              x=gint();y=gint();
          }
        PF("%d\n",tot);
        re(i,1,tot)PF("%d\n",out[i]);
        return 0;
  }

时间: 2024-10-25 05:57:31

NOI2011 兔兔与蛋蛋游戏的相关文章

【博弈+二分图匹配】[NOI2011]兔兔与蛋蛋游戏

题目描述 Description Input 输入的第一行包含两个正整数 n.m. 接下来 n行描述初始棋盘.其中第i 行包含 m个字符,每个字符都是大写英文字母"X".大写英文字母"O"或点号"."之一,分别表示对应的棋盘格中有黑色棋子.有白色棋子和没有棋子.其中点号"."恰好出现一次. 接下来一行包含一个整数 k(1≤k≤1000) ,表示兔兔和蛋蛋各进行了k次操作. 接下来 2k行描述一局游戏的过程.其中第 2i – 1

博弈论(二分图匹配):NOI 2011 兔兔与蛋蛋游戏

Description Input 输入的第一行包含两个正整数 n.m. 接下来 n行描述初始棋盘.其中第i 行包含 m个字符,每个字符都是大写英文字母"X".大写英文字母"O"或点号"."之一,分别表示对应的棋盘格中有黑色棋子.有白色棋子和没有棋子.其中点号"."恰好出现一次. 接下来一行包含一个整数 k(1≤k≤1000) ,表示兔兔和蛋蛋各进行了k次操作. 接下来 2k行描述一局游戏的过程.其中第 2i – 1行是兔兔的

bzoj 2437 [Noi2011]兔兔与蛋蛋 [二分图匹配]

描述 这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏. 这个游戏是在一个 n 行 m 列的棋盘上进行的.游戏开始之前,棋盘上有一 个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色. 每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为: 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格. 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格. 第一个不能按照规则操作的人输掉游戏. 最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友-- 你--来帮助

[匈牙利算法][博弈] Luogu P1971 兔兔与蛋蛋

题目描述 这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏. 这个游戏是在一个n行m列的棋盘上进行的.游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色. 每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为: 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格. 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格. 第一个不能按照规则操作的人输掉游戏.为了描述方便,下面将操作“将第x行第y列中的棋子移进空格中”记为M(x,y). 例如下面

CQUOJ D. 会做题的兔兔

D. 会做题的兔兔 Time Limit: 2000ms Memory Limit: 65536KB 64-bit integer IO format: %lld      Java class name: Main Submit Status 大家都听说梅小姐喂养了很多兔兔.梅小姐的兔兔超级萌.超级听话,经常能帮助梅小姐AC题目. 有一天,梅小姐给兔兔们一个数字,然后命令兔兔们去寻找有多少个不同的集合满足集合内的元素相加等于这个数字,并且兔兔们找的每个数都只能是2的k次幂. 比如: 梅小姐给了

bzoj 2437: [Noi2011]兔兔与蛋蛋

Description Solution 考虑犯错误的条件:之前是处于必胜状态,该操作之后就变成了必败状态. 我们可以把这个过程看成两人对网格图进行黑白染色,变成了一个二分图模型,即当前位置向相邻不同颜色的位置连边,构成的二分图,一次游戏相当于一个最大匹配. 一个结论:如果一定存在包含当前位置的最大匹配,那么处于先手必胜状态 证明: 因为当前点不处于最大匹配中,那么只有非匹配边可以走,假设走到了\(v\),\(v\)点则可以走匹配边,假设走了一条匹配边,则到达的下一个点只能走非匹配边,因为匹配的

bzoj2437 [Noi2011]兔兔与蛋蛋

二分图博弈果然都是一个套路,必经点必胜,非必经点必败, 但是肯定不能没走一步就重新建图判断必胜还是必败,那么我们可以这样:每走一步就把这个点删掉,然后find他原来的匹配,如果找不到,就说明他是必经点,否则就是非必经点. 1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <cmath> 6 #define

【bzoj2432】【NOI2011】兔农

题目描述 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小 朋友在讨论兔子繁殖的问题. 问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这 对兔子从第三个月开始,每个月初生一对小兔子.新出生的小兔子生长两个月后 又能每个月生出一对小兔子.问第 n 个月有多少只兔子? 聪明的你可能已经发现,第 n 个月的兔子数正好是第 n 个 Fibonacci(斐波那 契)数.栋栋不懂什么是 Fibonacci 数,但他也发现了规律:第 i+2 个月的兔子数 等于第 i 个月的

[bzoj2437]兔兔与蛋蛋

移动可以理解为空白格的移动,问题等价于双方在一张无向图(相邻不同色点连边,起点视为黑色)移动,不能经过重复的点,无法移动者为负由于这张图是二分图,因此有结论,先手必胜当且仅当起点一定在任意一组最大匹配中证明:必要性,即先手必胜=>一定在匹配中,其等价于不在匹配中=>后手必胜,考虑一组最大匹配,容易发现先手所走到的点一定在最大匹配中(否则匹配可以增大),而后手的策略就是一直走到那个点的匹配上,一定必胜充分性,即一定在匹配中=>先手必胜,那么当去掉起点后的最大匹配中,一定存在某一个点使得其可