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
",i,j);
return 0;
}