題解 P4055 【[JSOI2009]遊戲】
阿新 • • 發佈:2020-11-18
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; }