1. 程式人生 > 實用技巧 >【LOJ6033】「雅禮集訓 2017 Day2」棋盤遊戲(二分圖博弈)

【LOJ6033】「雅禮集訓 2017 Day2」棋盤遊戲(二分圖博弈)

點此看題面

  • 給定一張\(n\times m\)的棋盤,上面有一些障礙格。
  • 選擇一個非障礙格放一個棋子,先手後手輪流將它移到一個相鄰的沒有到過的非障礙格,無法移動的人輸。
  • 求出所有滿足先手必輸的非障礙格。
  • \(n,m\le100\)

挺久以前做過這道題的加強版:【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\n",t),i=1;i<=t;++i) printf("%d %d\n",sx[i],sy[i]);return 0;//輸出答案
}