【Tarjan算法】【DFS】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Th...

本文介绍了一种基于割边的算法实现,该算法用于解决平面内特定条件下点集是否能形成完美匹配的问题。通过构建二分图并利用深度优先搜索(DFS)进行边-双连通分量缩点来找出所有可能的割边。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这份代码可以作为找割边的模板。割边分割出来的部分是无向图的 边-双连通分量。

平面上2*n+1个点,在同一横坐标上的点之间可以任意两两匹配。同一纵坐标上的点之间也可以。问你对于所有的点i,输出i被移除之后,剩余的点能否完美匹配。

把x坐标当一列点,y坐标当一列点,原本的点当做边,建出来一个二分图。

一个连通块可以完美匹配,当且仅当其中边数为偶数。必须所有连通块的边数都是偶数,整个图才可以完美匹配。

考虑移除一个点,如果它不是割边,那么仅仅会让其所在连通块大小-1。如果其是割边,那么将其所在连通块分割成了两个连通块。就很容易在dfs的过程中统计答案。

可以做 边-双连通分量 缩点。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,K,xs[410000],ys[410000];
int e,first[410000],next[410000],v[410000],id[410000];
void AddEdge(int U,int V,int ID){
	v[e]=V;
	id[e]=ID;
	next[e]=first[U];
	first[U]=e++;
}
bool bridge[410000];
int dep,dfn[410000];
int Tarjan(int U,int te)
{
    int lowU=dfn[U]=++dep;
    for(int i=first[U];i!=-1;i=next[i])
      if(!dfn[v[i]])
        {
          int lowV=Tarjan(v[i],id[i]);
          lowU=min(lowU,lowV);
          if(lowV>dfn[U])
            bridge[i]=bridge[i^1]=1;
        }
      else if(id[i]!=te && dfn[v[i]]<dfn[U])
        lowU=min(lowU,dfn[v[i]]);
    return lowU;
}
bool vis[410000];
int cmp[410000];
bool anss[210000];
int siz[410000],cmp_sz[410000],siz2[410000];
void dfs(int U){
	vis[U]=1;
	cmp[U]=K;
	for(int i=first[U];i!=-1;i=next[i]){
		if(!vis[v[i]] && !bridge[i]){
			dfs(v[i]);
		}
	}
}
int nows[410000];
void df1(int U){
	vis[U]=1;
	for(int i=first[U];i!=-1;i=next[i]){
		++nows[K];
		if(!vis[v[i]]){
			df1(v[i]);
		}
	}
}
int jis;
void df2(int U){
	vis[U]=1;
	for(int i=first[U];i!=-1;i=next[i]){
		if(!bridge[i]){
			if(jis==1 && (nows[K]&1)){
				anss[id[i]]=1;
			}
		}
		if(!vis[v[i]]){
			df2(v[i]);
		}
	}
}
void df3(int U){
	vis[U]=1;
	siz[U]=cmp_sz[U];
	siz2[U]=1;
	for(int i=first[U];i!=-1;i=next[i]){
		if(!vis[v[i]]){
			df3(v[i]);
			siz[U]+=siz[v[i]];
			siz2[U]+=siz2[v[i]];
		}
	}
}
void df4(int root,int U){
	vis[U]=1;
	for(int i=first[U];i!=-1;i=next[i]){
		if(!vis[v[i]]){
			if(jis==1 && (siz2[root]-1+siz[root])%2==1 &&
			(siz2[v[i]]-1+siz[v[i]])%2==0 &&
			(siz2[root]-siz2[v[i]]-1+siz[root]-siz[v[i]])%2==0){
				anss[id[i]]=1;
			}
			df4(root,v[i]);
		}
	}
}
void df5(int U){
	vis[U]=1;
	for(int i=first[U];i!=-1;i=next[i]){
		if(cmp[U]==cmp[v[i]]){
			++cmp_sz[cmp[U]];
		}
		if(!vis[v[i]] && cmp[U]==cmp[v[i]]){
			df5(v[i]);
		}
	}
}
int main(){
//	freopen("b.in","r",stdin);
	scanf("%d",&n);
	n=n*2+1;
//	n=n;
	memset(first,-1,sizeof(first));
	for(int i=1;i<=n;++i){
		scanf("%d%d",&xs[i],&ys[i]);
		AddEdge(xs[i],ys[i]+n,i);
		AddEdge(ys[i]+n,xs[i],i);
	}
	for(int i=1;i<=n*2;++i){
		if(!dfn[i]){
			Tarjan(i,-1);
		}
	}
	for(int i=1;i<=2*n;++i){
		if(!vis[i]){
			++K;
			df1(i);
			nows[K]>>=1;
			if(nows[K]&1){
				++jis;
			}
		}
	}
	K=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=2*n;++i){
		if(!vis[i]){
			++K;
			df2(i);
		}
	}
	K=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n*2;++i){
		if(!vis[i]){
			++K;
			dfs(i);
		}
	}
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n*2;++i){
		if(!vis[i]){
			df5(i);
			cmp_sz[cmp[i]]>>=1;
		}
	}
	e=0;
	memset(first,-1,sizeof(first));
	for(int i=1;i<=n;++i){
		if(cmp[xs[i]]!=cmp[ys[i]+n]){
			AddEdge(cmp[xs[i]],cmp[ys[i]+n],i);
			AddEdge(cmp[ys[i]+n],cmp[xs[i]],i);
		}
	}
	jis=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=K;++i){
		if(!vis[i]){
			df3(i);
			if((siz2[i]-1+siz[i])&1){
				++jis;
			}
		}
	}
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=K;++i){
		if(!vis[i]){
			df4(i,i);
		}
	}
	for(int i=1;i<=n;++i){
		puts(anss[i] ? "OK" : "NG");
	}
	return 0;
}

转载于:https://www.cnblogs.com/autsky-jadek/p/7376673.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值