1. 程式人生 > 實用技巧 >題解 P4055 【[JSOI2009]遊戲】

題解 P4055 【[JSOI2009]遊戲】

題目連結

Solution [JSOI2009]遊戲

題目大意:給定一個 \(n\)\(m\) 列的棋盤,有些格子不能放棋子,\(A\) 選擇一個位置放棋子,然後 \(B,A\) 輪流操作,每次可以將棋子移動到相鄰位置,同一個位置不能被放兩次,求放在哪些位置 \(A\) 先手必勝

二分圖博弈,網路流


分析:

棋盤上棋子相鄰移動是一個比較經典的二分圖模型,問題可以變化成一個二分圖博弈問題。

如果這個二分圖有完全匹配,那麼先手必敗。無論先手選哪個位置,後手只要沿著匹配邊走就可以獲勝。

反之,只要先手選非匹配點,就可以把後手逼入匹配點,轉換為上一個問題從而先手必勝。

而如果對於所有匹配方案,一個點都是非匹配點,那麼先手選這個位置必勝。

因此可以想到暴力演算法,每次刪一個點,看一下最大匹配是否保持不變。

每次暴力跑 \(Dinic\) 複雜度無法接受,我們初始跑一次 \(Dinic\) ,然後分類討論。

  • 一個點是非匹配點,那麼刪去它之後最大匹配還是不變。
  • 一個點是匹配點,類似於匈牙利演算法,我們找出一條交替路,這樣交換路徑上的邊的匹配情況之後,最大匹配仍然不變,但是這個點變成了非匹配點從而可以被刪除。

考慮殘量網路,我們記源匯為 \(S,T\),分別與它們相連的點集為 \(X,Y\),那麼答案為:

  • \(S\) 出發,走所有非滿流邊,能夠走到的所有 \(X\) 點集的點。
  • \(T\) 出發,走所有滿流邊,能夠走到的所有 \(Y\)
    點集的點。

第一種情況,相當於找到了一條 非匹配-匹配--非匹配 \(\cdots\) 匹配路徑,且路徑起點是最初的非匹配點

第二種情況同理

程式碼請使用 C++17 編譯

//-std=c++17
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
constexpr int maxn = 128,dx[] = {-1,1,0,0},dy[] = {0,0,-1,1};
namespace dinic{
	constexpr int maxn = ::maxn * ::maxn,maxm = maxn << 3;
    typedef int type;
    struct edge{int v;type cap;}edges[maxm];
    int head[maxn],nxt[maxm],tot = 1;
    inline void clear(){
		for(int i = 2;i <= tot;i++)
			head[edges[i].v] = 0,nxt[i] = 0;
		tot = 1;
	}
	inline void addedge(int u,int v,type d){
        edges[++tot] = edge{v,d};
        nxt[tot] = head[u];
        head[u] = tot;
        edges[++tot] = edge{u,0};
        nxt[tot] = head[v];
        head[v] = tot;
    }
    int d[maxn];
    inline bool bfs(int s,int t){
        memset(d,-1,sizeof(d));
        queue<int> q;
        q.push(s);d[s] = 0;
        while(!q.empty()){
            int u = q.front();q.pop();
            for(int i = head[u];i;i = nxt[i]){
                const edge &e = edges[i];
                if(e.cap && d[e.v] == -1){
                    d[e.v] = d[u] + 1;
                    q.push(e.v);
                }
            }
        }
        return d[t] != -1;
    }
    int cur[maxn];
    inline type dfs(int u,type a,int t){
        if(u == t || !a)return a;
        type res = 0,f;
        for(int &i = cur[u];i;i = nxt[i]){
            const edge &e = edges[i];
            if(d[u] + 1 == d[e.v] && (f = dfs(e.v,min(a,e.cap),t))){
                res += f;
                edges[i].cap -= f;
                edges[i ^ 1].cap += f;
                a -= f;
                if(!a)break;
            }
        }
        return res;
    }
    inline type maxflow(int s,int t){
        type res = 0;
        while(bfs(s,t)){
            memcpy(cur,head,sizeof(head));
            res += dfs(s,0x7fffffff,t);
        }
        return res;
    }
}
int n,m,ss,tt,ans[maxn][maxn],vis[maxn * maxn];
char mp[maxn][maxn];
pair<int,int> decode[maxn * maxn];
int encode[maxn][maxn];
inline void dfs(const int u,const int cp){
	if(vis[u])return;
	vis[u] = 1;
	const auto [x,y] = decode[u];
	if(((x + y) & 1) == cp)ans[x][y] = 1;
	for(int i = dinic::head[u];i;i = dinic::nxt[i]){
		const dinic::edge &e = dinic::edges[i];
		if(e.cap != cp || e.v == ss || e.v == tt)continue;
		dfs(e.v,cp);
	}
}
inline bool chk(int x,int y){return x >= 1 && x <= n && y >= 1 && y <= m;}
inline void build(){
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			if(mp[i][j] == '.'){
				if((i + j) & 1)dinic::addedge(ss,encode[i][j],1);
				else dinic::addedge(encode[i][j],tt,1);
			}
	for(int x = 1;x <= n;x++)
		for(int y = 1;y <= m;y++)
			if((x + y) & 1 && mp[x][y] == '.')
				for(int i = 0;i < 4;i++){
					const int nx = x + dx[i],ny = y + dy[i];
					if(!chk(nx,ny) || mp[nx][ny] == '#')continue;
					dinic::addedge(encode[x][y],encode[nx][ny],1);
				}
}
int main(){
	scanf("%d %d",&n,&m);ss = n * m + 1,tt = ss + 1;
	int tot = 0;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			encode[i][j] = ++tot,decode[tot] = make_pair(i,j);
	for(int i = 1;i <= n;i++)scanf("%s",mp[i] + 1);
	int cnt[2];cnt[0] = cnt[1] = 0;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			if(mp[i][j] == '.')cnt[(i + j) & 1]++;
	build();
	int mx = dinic::maxflow(ss,tt);
	if(cnt[0] == cnt[1] && mx == cnt[0])return puts("LOSE"),0;
	dfs(ss,1);
	memset(vis,0,sizeof(vis));
	dfs(tt,0);
	puts("WIN");
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			if(mp[i][j] == '.'&& ans[i][j])printf("%d %d\n",i,j);
	return 0;
}