洛谷1971 BZOJ2437 NOI2011 兔兔与蛋蛋 二分图匹配

题目链接

题意:
有一个 n ∗ m n*m nm的棋盘,每个格子有一个黑棋子或者白棋子,整个棋盘只有一个空格子。有两个人在做游戏,第一个人要把与空格四连通的一个白棋子走到空格,第二个人要把一个与空格四连通的黑棋子走到空格,两人轮流走,谁不能走谁就输。现在给你输入一局对局的进行情况,你要判断先手有多少次失误,失误的定义是,如果第一个人从一个有必胜策略的格子,走到一个第一个人必败的格子,就算是失误。输出失误次数以及具体的错误操作编号。

题解:
怎么说呢,这个题要做的话要么你要推很多结论,要么你要知道很多结论,或者你有很强的显然感知能力和思维能力。

我们把这个过程看着是在棋盘上移动这个空格子。首先是可以证明一个格子只会被空格子经过最多一次。证明的方法是一个格子是第一次经过它的时候,它如果是与一个白棋子交换,那么下一次走到这里的前一步一定是空格子需要与一个黑棋子交换位置。原因是你不论怎么走,最后回到自己,那么经过的步数一定是偶数步。我们可以知道,我们与当前空格子交换的一定是黑白棋子交替,那么偶数次之后所需要的黑白颜色不变,但是我们第一次换之前那个格子上是一个黑棋子,但是换完之后变成了一个白棋子,再之后要想换过去仍然需要黑棋子,于是就永远无法再把空格子移过去了。

证明完这个之后,我们从刚才的证明中可以发现,我们黑白交替的过程其实类似一个黑白染色,而黑白交替并且不能重复经过地走类似与在一个二分图上走增广路。这样我们可以对原图建出一个二分图。建图的方法是对于所有轮到先手走的格子,向四周可以到达的轮到后手走的点,简而言之,就是所有白点和一开始的空格向周围的黑点连边。我们考虑获胜的情况,获胜就是要自己走完之后对方走不了, 我们考虑相当于在二分图上走,不停地从一侧走到另一侧,不能走原来走过的点,直到不能走了,到谁那侧停了就是谁不能走了,也就是谁输了。我们考虑先对二分图做一个匹配,那么我们走的过程就应该是在二分图上的匹配边与非匹配边交替走,交替走奇数步是先手赢,走偶数步是后手赢。我们发现如果能在一个二分图上走奇数步,意味着找到了一条增广路。那么如果一个在先手一侧的点无论怎么样都在最大匹配上,从它出发不断走下去,直到不能走为止,一定会是一条长度为奇数的增广路径,而此时这个点就是一个先手必胜的点。我们判断一个点是否在最大匹配上的方案是,删除这个点与匹配点之间的匹配,然后从它原来的匹配点开始再次尝试增广,看是否能增广成功,如果不能成功,那么当前点就一定是在最大匹配上。这样对于每一次操作,我们都判断一下走之前和走之后的点是否是必胜状态,如果先手走之前是必胜状态,走完之后变成了后手先走的必胜状态,就意味着先手出现了一次操作错误。最后复杂度就是匈牙利的复杂度。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,k,mat[100010],cnt,id[210][210],xx,yy,bk[210][210],ji[500010],vis[500010];
char s[201][210];
int ans[4010],num,win[4010],hed[500010];
struct node
{
    int to,next;
}a[500010];
inline void add(int from,int to)
{
    a[++cnt].to=to;
    a[cnt].next=hed[from];
    hed[from]=cnt;
}
inline int match(int x)
{
    if(ji[x])
    return 0;
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(vis[y]==cnt||ji[y])
        continue;
        vis[y]=cnt;
        if(match(mat[y])||(!mat[y]))
        {
            mat[x]=y;
            mat[y]=x;
            return 1;
        }
    }
    return 0; 
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    scanf("%s",s[i]+1);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            id[i][j]=++cnt;
            if(s[i][j]=='O')
            bk[i][j]=0;
            else if(s[i][j]=='X')
            bk[i][j]=1;
            else
            {
                bk[i][j]=1;
                xx=i;
                yy=j;
            }
        }
    }
    cnt=0;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            if(bk[i][j]==0)
            continue;
            if(i>1)
            {
                if(!bk[i-1][j])
                {
                    add(id[i][j],id[i-1][j]);
                    add(id[i-1][j],id[i][j]);
                }
            }
            if(j>1)
            {
                if(!bk[i][j-1])
                {
                    add(id[i][j],id[i][j-1]);
                    add(id[i][j-1],id[i][j]);
                }
            }
            if(i<n)
            {
                if(!bk[i+1][j])
                {
                    add(id[i][j],id[i+1][j]);
                    add(id[i+1][j],id[i][j]);
                }
            }
            if(j<m)
            {
                if(!bk[i][j+1])
                {
                    add(id[i][j],id[i][j+1]);
                    add(id[i][j+1],id[i][j]);
                }
            }
        }
    }
    cnt=0;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            if(bk[i][j])
            {
                ++cnt;
                match(id[i][j]);
            }
        }
    }
    scanf("%d",&k);
    for(int i=1;i<=2*k;++i)
    {
        int x=id[xx][yy];
        ji[x]=1;
        if(mat[x])
        {
            int y=mat[x];
            mat[y]=0;
            mat[x]=0;
            ++cnt;
            win[i]=match(y)^1;
        }
        scanf("%d%d",&xx,&yy);
    }	
    for(int i=1;i<=k;i++)
    {
        if(win[(i<<1)-1]&&win[(i<<1)])
        ans[++num]=i;
    }
    printf("%d\n",num);
    for(int i=1;i<=num;++i)
    printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值