zoukankan      html  css  js  c++  java
  • 【LOJ6033】「雅礼集训 2017 Day2」棋盘游戏(二分图博弈)

    点此看题面

    • 给定一张(n imes m)的棋盘,上面有一些障碍格。
    • 选择一个非障碍格放一个棋子,先手后手轮流将它移到一个相邻的没有到过的非障碍格,无法移动的人输。
    • 求出所有满足先手必输的非障碍格。
    • (n,mle100)

    挺久以前做过这道题的加强版:【BZOJ2437】[NOI2011] 兔兔与蛋蛋

    当时觉得好神仙,怎么想到二分图的(尽管现在依然这么想),如今才知道原来这一切都是套路。

    二分图博弈

    首先发现如果我们给这张棋盘黑白染色,然后在所有相邻的非障碍格之间连边,建出来的应该是一张二分图。

    然后就有一个结论,一个点是必胜点,当且仅当去掉它之后二分图最大匹配数量会减少(显然减少的话只会减(1))。

    假设在除去当前点后仍存在一个最大匹配,那我们从当前点必然只能沿非匹配边(如果没有,就说明输了)走向另一端的一个点,而另一端的点必然能够通过一条匹配边(必然存在,否则就不是最大匹配了)走回来。

    也就是说,从这边不一定能找到一条边过去,但从那边却一定能找到一条边回来,显然某一刻无法再过去,那么就输了。

    反之,若除去当前点后不存在最大匹配了,按照类似的证明步骤,便可得当前决胜者存在必胜策略。

    而要判断去掉一个点之后最大匹配是否会减少,就是要看原本与它匹配的点能否找到一个新的匹配点,直接匈牙利算法即可。

    代码:(O(n^2m^2))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100
    #define P(x,y) (((x)-1)*m+(y))
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,m,ee,lnk[N*N+5],sx[N*N+5],sy[N*N+5];
    struct edge {int to,nxt;}e[4*N*N+5];char s[N+5][N+5];
    namespace H//匈牙利算法
    {
    	int vis[N*N+5],s[N*N+5],del[N*N+5];I bool Match(CI x,CI w,CI ti)//w表示当前删去的点
    	{
    		for(RI i=lnk[x];i;i=e[i].nxt)
    		{
    			if(vis[e[i].to]==ti||e[i].to==w) continue;vis[e[i].to]=ti;
    			if(!s[e[i].to]||Match(s[e[i].to],w,ti)) return s[s[e[i].to]=x]=e[i].to;
    		}return 0;
    	}
    }
    int main()
    {
    	RI i,j,ti=0;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",s[i]+1);
    	for(i=1;i<=n;++i) for(j=1;j<=m;++j)//建二分图
    		s[i][j]=='.'&&s[i][j+1]=='.'&&(add(P(i,j),P(i,j+1)),add(P(i,j+1),P(i,j))),
    		s[i][j]=='.'&&s[i+1][j]=='.'&&(add(P(i,j),P(i+1,j)),add(P(i+1,j),P(i,j)));
    	for(i=1;i<=n;++i) for(j=1;j<=m;++j) s[i][j]^'#'&&!H::s[P(i,j)]&&H::Match(P(i,j),0,++ti);//求出初始匹配
    	RI x,y,t=0;for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(s[i][j]^'#') x=P(i,j),y=H::s[x],//枚举每个点判断是否为必输点
    		(!y||(H::s[x]=H::s[y]=0,H::Match(y,x,++ti)))?(sx[++t]=i,sy[t]=j):H::Match(x,0,++ti);//本来就不在最大匹配中,或去掉之后最大匹配数不变
    	for(printf("%d
    ",t),i=1;i<=t;++i) printf("%d %d
    ",sx[i],sy[i]);return 0;//输出答案
    }
    
  • 相关阅读:
    第十二章 圆周率的计算问题分析
    第十一章:random库概述
    【模板】分治 FFT
    [PKUWC2018]Slay the Spire
    [PKUWC2018]随机算法
    [PKUWC2018]Minimax
    线段树合并初探
    平衡树初探
    Luogu P1613 跑路 题解报告
    CH138 兔子和兔子 题解报告
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ6033.html
Copyright © 2011-2022 走看看