NOI2011 兔兔与蛋蛋(博弈论+二分图)

题目

分析:

把移动的过程看做是中间空格在走,则空格一定是在黑格子与白格子间交替移动,这就变成了一个二分图的模型。

通过题目可以得出两个性质

1.棋盘上的每一点最多只被走一次

2.如果兔兔将要移动的空格对应的点一定在二分图的最大匹配上,兔兔必胜。

性质2的原因口胡:空格在最大匹配中,那么沿着匹配边走一个点,一定能走到下一个黑点,而黑点不一定能找到下一条边走到白点,兔兔必胜。

如何判断那个点是否一定在最大匹配上呢?

如果强制把那个点去掉,看是否能找到增广路,如果不能,就说明那个点一定在最大匹配上。

两个性质的详细证明与分析

 但是题中要求的是错误步骤,应该怎么转换呢?

把走的每一步转换成一个新的局面,然后通过上述性质去check此局面是先手必胜还是必败。

#include<bits/stdc++.h>
using namespace std;
#define ri register int 
#define N 2500
#define nn 50
int id[nn][nn],a[nn][nn],fl[N],to[N<<1],nex[N<<1],head[N],mat[N],vis[N],ans[N],win[N];
int dx[5]={0,0,1,-1},dy[5]={1,-1,0,0},tot=0,T=0;
char s[nn];
int read()
{
    int x=0,fl=1; char ch=getchar();
    while(ch>'9'||ch<'0') { if(ch=='-') fl=-1; ch=getchar(); }
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
    return x;
}
void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; }
bool dfs(int x)
{
    if(fl[x]) return false;//被删除的点不会再被遍历到 
    for(ri i=head[x];i;i=nex[i]){
        int v=to[i];
        if(fl[v] || vis[v]==T) continue;
        vis[v]=T;
        if(!mat[v] || dfs(mat[v])){
            mat[x]=v;
            mat[v]=x;//最好记录两边 保证不重不漏 
            return true;
        }
    }
    return false;
}
int main()
{
    //freopen("game.in","r",stdin);
    //freopen("game.out","w",stdout); 
    int n,m,x,y,cnt=0;
    n=read(); m=read();
    for(ri i=1;i<=n;++i) for(ri j=1;j<=m;++j) id[i][j]=++cnt;
    for(ri i=1;i<=n;++i){
        scanf("%s",s);
        for(ri j=1;j<=m;++j){
            a[i][j]=(s[j-1]=='O')?0:1;
            if(s[j-1]=='.') x=i,y=j;
        }
    }
    for(ri i=1;i<=n;++i)
     for(ri j=1;j<=m;++j)
      for(ri k=0;k<=3;++k){
          if(a[i][j]==0) continue;//保证是黑点向白点连边 
          int xx=i+dx[k],yy=j+dy[k];
          if(xx<1 || xx>n || yy<1 || yy>m || a[xx][yy]==1) continue;
          add(id[i][j],id[xx][yy]),add(id[xx][yy],id[i][j]);
    }
    for(ri i=1;i<=n;++i)
     for(ri j=1;j<=m;++j)
      if(a[i][j]==0){
        ++T;
        dfs(id[i][j]);
    }
    //题目中给了每一次的移动方式 其实可以转变成:每一次在走完之前步后的剩余棋盘下 判断是否可以先手必胜 
    int op=read();
    cnt=0;
    for(ri i=1;i<=op*2;++i){
        int now=id[x][y];
        fl[now]=1;//打标记说明这个点已经被去掉 
        if(mat[now]){
            int v=mat[now];
            mat[now]=mat[v]=0;
            ++T;//++T是为了判断有没有绕环 所以每dfs一次都要++T 
            win[i]= !dfs(v);//如果dfs成功了 说明可以找到一条不包含now的增广路 now是必败点 标记成 0 
        }
        //如果这个点直接没有在最大匹配中 就应该为必败点 
        x=read(),y=read();
    }
    //如果兔兔本来这步可以赢 就说明这一步它走错了 因为最后一定是兔兔必输状态 
    //根据题意判断两次 
    for(ri i=1;i<=op;++i) if( win[i<<1] && win[(i<<1)-1] ) ans[++cnt]=i;
    printf("%d\n",cnt);
    for(ri i=1;i<=cnt;++i) printf("%d\n",ans[i]);
    return 0; 
}
/*
4 
OOXX 
OXXO 
OO.O 
XXXO

2 
2 
2 
3
*/

 

转载于:https://www.cnblogs.com/mowanying/p/11508339.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值