P4055 题解

题意

  • 在一个 \(n*m\) 的有障碍方阵中,两个人轮流移动棋子走到相邻的方格。

  • 先手决定棋子的初始位置。

  • 最后移动棋子的玩家获胜。

  • 问先手是否有必胜策略,如果有,输出所有能获胜的棋子初始位置。

分析

看到这种向相邻方格移动棋子的题,很容易想到黑白染色建二分图

相邻的方格只要没有障碍,就连一条双向边

每次操作可以转换为从左部走到右部或从右部走到左部。

那么,如果这个二分图是完全匹配,无论先手选哪个点,后手都会选最大匹配中这个点对应的点。

因此这时后手有必胜策略。

如果这个图不是完全匹配,一定有一个点剩下来,也就是最大匹配不需要的点。

先手只要一开始把棋子放在这个点,后手无论怎么走,走到的点都在最大匹配内。

于是后手就变成了第一种情况的先手,先手使用上面后手的策略。

因此这时先手有必胜策略。

因为最大匹配方案不止一种,所以所有最大匹配方案剩下的点都可以作为棋子的初始位置。

但是枚举每种方案的时间复杂度太大了。

我们可以先任意求一种方案,设剩下的点为 \(i\)

\(i\) 开始遍历二分图,假设 \(i\) 有一条边一个异侧点 \(j\)\(j\) 有一条在已求方案内的边连 \(i\)同侧点 \(k\),那么一定可以不选 \(j\)\(k\) 这条边,改选 \(i\)\(j\) 的边。

\(k\) 即改选后的方案剩下的点。

按照到现在为止的思路写就可以 AC 这道题。

但是当我看到讨论区的这个帖子后,发现我没有考虑图不连通的情况。然而造数据的人貌似也没有考虑。

其实很好解决,每个连通块都建一遍图就行。为了方便,我用了 \(\text{dfs}\) 建图。

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

int n,m,tot; //tot是联通块的编号 
int npy[10002]; //匹配方案 
char a[102][102];
int mark[102][102]; //每个点所属的联通块 
int cnt,head[10002]; //链式前向星 
int arrx[4]={0,1,0,-1};
int arry[4]={1,0,-1,0}; //方向数组 
bool vis[10002],ans[10002],lft[10002]; //lft记录每个点是否属于左部 

struct edge{
	int to,nxt;
}e[40002];

void addedge(int A,int B) {
	e[++cnt].to=B;
	e[cnt].nxt=head[A];
	head[A]=cnt;
}

int num(int x,int y) {
	return (x-1)*m+y;
} //坐标->编号 

void build(int x,int y) {
	for(int i=0;i<4;i++) {
		int xx=x+arrx[i],yy=y+arry[i];
		if(!xx || !yy || xx>n || yy>m || a[xx][yy]=='#') continue;
		addedge(num(x,y),num(xx,yy));
		if(mark[xx][yy]) continue;
		mark[xx][yy]=tot;
		build(xx,yy);
	}
} //每个联通块dfs建图 

bool dfs(int u) {
	for(int i=head[u];i;i=e[i].nxt) {
		int v=e[i].to;
		if(!vis[v]) {
			vis[v]=1;
			if(!npy[v] || dfs(npy[v])) {
				npy[v]=u,npy[u]=v;
				return 1;
			}
		}
	}
	return 0;
} //匈牙利 

void dfs2(int u,int rt) {
	if(ans[u]) return ;
	ans[u]=1;
	for(int i=head[u];i;i=e[i].nxt) {
		int v=e[i].to;
		if(npy[v]) dfs2(npy[v],rt);
	}
} //找其他最大匹配方案不需要的点 

int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) cin>>a[i]+1;
	bool anss=0;
	for(int i=1;i<=n;i++) 
		for(int j=1;j<=m;j++) 
			if(!mark[i][j] && a[i][j]=='.') {
				cnt=0,tot++;
				memset(head,0,sizeof head);
				memset(e,0,sizeof e);
				memset(npy,0,sizeof npy);
				build(i,j);
				for(int k=1;k<=n;k++) 
					for(int l=1;l<=m;l++) {
						if(mark[k][l]!=tot || (k+l)%2==0) continue;
						lft[num(i,j)]=1;
						memset(vis,0,sizeof vis);
						dfs(num(k,l));
					}
				bool flag=0;
				for(int k=1;k<=n;k++) 
					for(int l=1;l<=m;l++) 
						if(mark[k][l]==tot && !npy[num(k,l)]) flag=1;
				//判断是否为完全匹配 
				anss|=flag;
				if(!flag) continue; 
				for(int k=1;k<=n;k++) 
					for(int l=1;l<=m;l++) 
						if(mark[k][l]==tot && !npy[num(k,l)]) dfs2(num(k,l),num(k,l));
			}
	if(!anss) {
		printf("LOSE\n");
		return 0;
	}
	printf("WIN\n");
	for(int i=1;i<=n;i++) 
		for(int j=1;j<=m;j++) 
			if(ans[num(i,j)]) printf("%d %d\n",i,j);
	return 0;
}

码风不好看 TwT。

posted @ 2022-07-31 16:05  Sharing666  阅读(64)  评论(0)    收藏  举报