【BZOJ1443】游戏Game,博弈+二分图匹配

传送门
思路:
很巧妙的思路
将二分图的匹配与博弈相联系
给出定理:

若二分图中去掉起点后仍存在最大匹配(与先前的最大匹配数相等),那么先手必输;反之先手必胜

关于证明,我昨天晚上和今天中午想了很久,随便口胡一下

先考虑起点一定在最大匹配中(即定理中所说的’先手必胜’情况)的情况:
那么只要先手只要移到起点的匹配点就可以了
因为显然走的边是匹配边->非匹配边->匹配边->非匹配边…
如果表示一对匹配点为 (xi,yi) 的话(设起点为 x1
那么走的点就是 x1>y1>x2>y2...
也就是说只要对手(在y点)找到了一个(x点)
那么我们(在x点)一定可以走当前点的匹配点
最终肯定是对手输
会不会对手(在y点)不走到x点?也就是说对手走了一条非匹配边到达了非匹配点
显然是不能的
因为这样的话把之前路径上的匹配边变成非匹配边,非匹配边变成匹配边,这又是一个最大匹配,同时起点也不再是匹配点,这与给出的条件’起点一定在最大匹配中’矛盾,所以不成立

再考虑’先手必输’的情况
在这种情况下,我们可以把起点看做一个非匹配点
只能从起点出发走到一个已匹配点中,这个已匹配点一定在删除起点的二分图中的最大匹配里(至于证明的话,因为如果该点不在最大匹配中,那么起点和该点在原图中构成一个新的匹配,而原图匹配数应该等于现图匹配数,所以’该点不在最大匹配’不成立)
然后就转化成上面先手必胜的情况了

证毕

然后就可以暴力枚举一下了……
好像会T,不管了,压了压常数,跑了6000ms+……
注意每次跑匈牙利之前要清空vis数组,而且每次删除完点后还要再加进去,也就是再跑一遍匈牙利就可以了
代码:

#include<cstdio>
#include<cstring>
using namespace std;
int n,m,tot,cnt;
bool vis[10003],del[10003];
int a[103][103],id[103][103],first[10003],belong[10003],ax[10003],ay[10003];
int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
struct edge{
    int v,next;
}e[40005];
int in()
{
    char c=getchar();
    while (c!='.'&&c!='#') c=getchar();
    if (c=='.') return 1;
    return 0;
}
void add(int u,int v){e[++tot]=(edge){v,first[u]};first[u]=tot;}
bool find(int x)
{
    if (del[x]) return 0;
    for(int i=first[x];i;i=e[i].next)
        if (!del[e[i].v]&&!vis[e[i].v])
        {
            vis[e[i].v]=1;
            if (!belong[e[i].v]||find(belong[e[i].v]))
            {
                belong[e[i].v]=x;
                belong[x]=e[i].v;
                return 1;
            }
        }
    return 0;
}
main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
        {
            a[i][j]=in();
            if (a[i][j])
                id[i][j]=++cnt;
        }
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
            if (id[i][j])
                for (int k=0;k<4;++k)
                    if (id[i+dx[k]][j+dy[k]])
                        add(id[i][j],id[i+dx[k]][j+dy[k]]);
    for (int i=1;i<=cnt;++i)
        if (!belong[i]) 
        memset(vis,0,sizeof(vis)),
        find(i);
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
            if (id[i][j])
            {
                int tmp=belong[id[i][j]];
                belong[id[i][j]]=belong[tmp]=0;
                del[id[i][j]]=1;
                memset(vis,0,sizeof(vis));
                if (!tmp||find(tmp))
                {
                    ax[++ax[0]]=i,ay[ax[0]]=j;
                    memset(vis,0,sizeof(vis));
                    find(id[i][j]);
                }
                else belong[tmp]=id[i][j],belong[id[i][j]]=tmp;
                del[id[i][j]]=0;
            }
    if (ax[0]) puts("WIN");
    else puts("LOSE");
    for (int i=1;i<=ax[0];++i) printf("%d %d\n",ax[i],ay[i]); 
}
  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论

打赏作者

iamxym

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值