suxxsfe

一言(ヒトコト)

LOJ6033「雅礼集训 2017 Day2」棋盘游戏(二分图最大匹配必经点)

https://loj.ac/p/6033

考虑把棋盘黑板染色,然后给这个二分图跑最大匹配
某位置开始后手必胜,当且仅当存在一个最大匹配使得这个点不是匹配点
证明:若存在,则先手每次只需要走到上个点对应的匹配点,最终一定是后手无路可走;若不存在,则起始点旁边必定都是匹配点,先手走上去转化成存在的情况

所以问题变成了求二分图最大匹配必经点
考虑左部点,对于每个非匹配边,从左到右连有向边,对于匹配边,从右到左连有向边,那么从每个非匹配左部点开始遍历,遍历到的所有左部点都不是必经点(因为走出了一条交替路,所有匹配边变非匹配边,非匹配边变匹配边,那匹配点也全边非匹配点了)
对于右部点同理

令:二分图最大匹配可行边、必须边

最大匹配是完备匹配
把二分图中的非匹配边看作从左部向右部的有向边,匹配边看作从右部向左部的有向边,构成一张新的有向图。

  • 必须边 \((x,y)\)\((x,y)\) 是当前二分图的匹配边,且 \(x,y\) 在新的有向图中属于不同的强连通分量。
  • 可行边 \((x,y)\)\((x,y)\) 是当前二分图的匹配边, \(x,y\) 在新的有向图中属于同一个强连通分量。

最大匹配不一定是完备匹配

  • 必须边 \((x,y)\)\((x,y)\) 的流量为 \(1\),且在残量网络上属于不同的强连通分量。
  • 可行边 \((x,y)\)\((x,y)\) 的流量为 \(1\)在残量网络上属于同一个强连通分量。
#define N 20006
#define M 2000006
struct Graph{
	int fir[N],nex[M],to[M],tot;
	inline void add(int u,int v,int flag=1){
		to[++tot]=v;
		nex[tot]=fir[u];fir[u]=tot;
		if(flag) add(v,u,0);
	}
	inline void clear(){tot=0;std::memset(fir,0,sizeof fir);}
}G,T;
int match[N],vis[N];
int dfs(int u){
	for(int v,i=G.fir[u];i;i=G.nex[i]){
		v=G.to[i];
		if(vis[v]) continue;
		vis[v]=1;
		if(!match[v]||dfs(match[v])) return match[v]=u,match[u]=v,1;
	}
	return 0;
}
int n,m;
char s[106][106];int id[106][106];
int isLeft[N];
const int di[]={0,1,0,-1};
const int dj[]={1,0,-1,0};
inline void rebuild(int op){
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'&&((i+j)&1)){
		for(int i_,j_,k=0;k<4;k++){
			i_=i+di[k];j_=j+dj[k];
			if(i_<1||j_<1||i_>n||j_>m||s[i_][j_]=='#') continue;
			if((match[id[i][j]]==id[i_][j_])^op) T.add(id[i][j],id[i_][j_],0);
			else T.add(id[i_][j_],id[i][j],0);
		}
	}
}
inline void build(){
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'){
		for(int i_,j_,k=0;k<2;k++){
			i_=i+di[k];j_=j+dj[k];
			if(i_<1||j_<1||i_>n||j_>m||s[i_][j_]=='#') continue;
			G.add(id[i][j],id[i_][j_]);
		}
	}
}
int yes[N];
void dfs2(int u,int k){
	if(vis[u]) return;
	vis[u]=1;yes[u]=(isLeft[u]^k);
	for(int i=T.fir[u];i;i=T.nex[i]) dfs2(T.to[i],k);
}
inline void work(){
	rebuild(1);
	std::memset(vis,0,sizeof vis);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!match[id[i][j]]&&s[i][j]=='.'&&((i+j)&1)) dfs2(id[i][j],0);
	T.clear();
	rebuild(0);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!match[id[i][j]]&&s[i][j]=='.'&&!((i+j)&1)) dfs2(id[i][j],1);
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		scanf("%s",s[i]+1);
		for(int j=1;j<=m;j++)if(s[i][j]=='.') id[i][j]=++id[0][0],isLeft[id[0][0]]=((i+j)&1);
	}
	build();
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(((i+j)&1)&&s[i][j]=='.'){
		std::memset(vis,0,sizeof vis);
		dfs(id[i][j]);
	}
	work();
	int ans=0;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'&&(!match[id[i][j]]||yes[id[i][j]])) ans++;
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'&&(!match[id[i][j]]||yes[id[i][j]])) printf("%d %d\n",i,j);
	return 0;
}
posted @ 2021-10-16 17:53  suxxsfe  阅读(67)  评论(0编辑  收藏  举报