zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",i,j);
    	return 0;
    }
    
  • 相关阅读:
    接口
    echartsx轴名称过长,截断+鼠标划过显示全称
    浏览器兼容的几点思路
    安装gulp教程(整理)
    TortoiseSVN文件夹及文件图标、标识、绿色小对号不显示解决方法(转载)
    css实现小三角(转载+个人笔记)
    css常用样式(待更新)
    表格样式设计和几点考量
    一些大神或者觉得有益的博客、专栏等(不定时更新)
    搭配bootstracp运用的通用样式(想起来就开个头,待补充……)
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/15414767.html
Copyright © 2011-2022 走看看