SCC+EDCC+VDCC 代码

有向图

SCC(强连通分量)计数

SCC 缩点后容易在新图中出现重边,这在某些题来说会导致错误的结果。一般来说我们可以如下处理:

显然首先对于 SCC 内连边是肯定不需要在新图中连出的。

  1. 使用 map<int,int> 记录 \((u,v)\) 是否出现过,连边 \(O(n\log n)\)

  2. 先不去重地连边,然后依次找第 \(i\) 个 SCC,对于每个过程开一个 \(vis[id]\) 记录是否存在 \((x,id)\) 这条边,用 vector<int> 存边,每次第一次找到 \((x,id)\)\(vis[id]\leftarrow 1\),最后对所有边 \(vis[id]\leftarrow 0\) 即可,连边 \(O(n)\)

Tarjan:

V1(New Version)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+5;
int n,m,a[N],in[N];
vector<int>G[N],P[N];
int U[N],V[N];
int scno[N],dfn[N],low[N],tms;
int stk[N],tp,scnt,sum[N],f[N];
void tarjan(int u){
	stk[++tp]=u;
	dfn[u]=low[u]=++tms;
	for(int v:G[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!scno[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		int tem=0;
		scnt++;
		do{
			tem=stk[tp--];
			scno[tem]=scnt;
			sum[scnt]+=a[tem];
		}while(tem!=u);
	}
}
int hd=1,tl,q[N],ans;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=m;i++){
		int u,v;cin>>u>>v;
		U[i]=u,V[i]=v;
		G[u].emplace_back(v);
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i])tarjan(i);
	for(int i=1;i<=m;i++){
		int u=U[i],v=V[i];
		if(scno[u]!=scno[v]){
			P[scno[u]].emplace_back(scno[v]);
			in[scno[v]]++;
		}
	}
	for(int i=1;i<=scnt;i++)
		if(!in[i])q[++tl]=i,f[i]=sum[i];
	while(hd<=tl){
		int u=q[hd++];
		ans=max(ans,f[u]);
		for(int v:P[u]){
			in[v]--;
			f[v]=max(f[v],f[u]+sum[v]);
			if(!in[v])q[++tl]=v;
		}
	}
	cout<<ans;
	return 0;
}

V2(Old Version)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int h[N],e[N],ne[N],idx=1;
int dfn[N],low[N],sccno[N],num[N],timestamp;
int stk[N],top;
int n,m,scc_cnt,res;
void connec(int x,int y){
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void tarjan(int x){
	dfn[x]=low[x]=++timestamp;
	stk[top++]=x;
	for(int i=h[x];i;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			tarjan(j);
			low[x]=min(low[x],low[j]);
		}
		else if(!sccno[j]){
			low[x]=min(low[x],dfn[j]);
		}
	}
	if(dfn[x]==low[x]){
		scc_cnt++;
		while(1){
			int tem=stk[--top];
			sccno[tem]=scc_cnt;
			num[scc_cnt]++;
			if(x==tem)break;
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		connec(x,y);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			tarjan(i);
		}
	}
	for(int i=1;i<=scc_cnt;i++){
		if(num[i]>0)res++;
	}
	printf("%d",res);
	return 0;
}

Kosaraju:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int sccno[N],n,m,res;
vector<int>G[N],rG[N];
vector<int>stk;
bool vis[N];
void dfs(int x){
	if(vis[x])return ;
	vis[x]=1;
	for(int i=0;i<G[x].size();i++){
		int j=G[x][i];
		dfs(j);
	}
	stk.push_back(x);
}
void dfs2(int x){
	if(sccno[x])return ;
	sccno[x]=res;
	for(int i=0;i<rG[x].size();i++){
		int j=rG[x][i];
		dfs2(j);
	}
}
void Kosaraju(){
	for(int i=1;i<=n;i++)dfs(i);
	for(int i=n-1;i>=0;i--){
		if(!sccno[stk[i]]){
			res++;
			dfs2(stk[i]);
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		rG[y].push_back(x);
	}
	Kosaraju();
	printf("%d",res);
	return 0;
}

无向图

半动态维护 EDCC

考虑对一个无向图先跑出一棵最小生成树(边权是加入顺序),然后对于非树边相当于实行链覆盖,每次合并路径上若干个边双,直接用并查集维护可以做到总合并次数 \(O(n)\),总时间 \(O(n\alpha(n))\)。具体可见(笔记)树上启发式合并 DSU on tree

EDCC(边双联通分量)计数

无重边 vector 版
vector<int>P[N];
int stk[M],dfn[N],low[N],tms,tp;
int id[N],ecnt,ra[N];
void tarjan(int u,int fa){
	dfn[u]=low[u]=tms++;
	stk[tp++]=u;
	for(int v:P[u]){
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
		}
		else if(v!=fa)
			low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		ecnt++;
		int tem;
		do{
			tem=stk[--tp];
			ra[ecnt]+=a[tem];
			id[tem]=ecnt;
		}while(tem!=u);
	}
}
有重边链式前向星
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int head[N],e[N],ne[N],stk[N],dfn[N],low[N],n,m,res,ti,top,idx,tj[N];
int id[N],ecnt;
bool isc[N];
void connec(int x,int y){
	e[idx]=y;
	ne[idx]=head[x];
	head[x]=idx++;
}
void tarjan(int u,int fr){
	dfn[u]=low[u]=ti++;
	stk[top++]=u;
	for(int i=head[u];i!=-1;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			tarjan(j,i);
			low[u]=min(low[u],low[j]);
			if(low[j]>dfn[u]){
				isc[i]=isc[i^1]=1;
			}
		}
		else if(i!=(fr^1)){
			low[u]=min(low[u],dfn[j]);
		}
	}
	if(low[u]==dfn[u]){
		ecnt++;
		int tem;
		do{
			tem=stk[--top];
			id[tem]=ecnt;
		}while(tem!=u);
	}
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		connec(a,b);
		connec(b,a);
	}
	tarjan(1,-1);
	printf("%d",ecnt);
	return 0;
}

VDCC(点双联通分量)计数

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef unsigned long long ull;
int h[N],e[N],ne[N],idx;
int dfn[N],low[N],stk[N],top,timestamp;
vector<int>dcc[N];
int n,m,root;
ull dcc_cnt;
ull cnt,res;
bool isc[N];
void connec(int x,int y){
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void tarjan(int x){
	dfn[x]=low[x]=++timestamp;
	stk[top++]=x;
	if(x==root&&h[x]==-1){
		dcc_cnt++;
		dcc[dcc_cnt].push_back(x);
		return ;
	}
	int child=0;
	for(int i=h[x];~i;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			tarjan(j);
			child++;
			low[x]=min(low[x],low[j]);
			if(low[j]>=dfn[x]){
				if(x!=root||child>1)isc[x]=1;
				++dcc_cnt;
				int tem=0;
				do{
					tem=stk[--top];
					dcc[dcc_cnt].push_back(tem);
				}while(j!=tem);
				dcc[dcc_cnt].push_back(x);
			}
		}
		else low[x]=min(low[x],dfn[j]);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)h[i]=-1;
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		connec(x,y);
		connec(y,x);
	}
	for(root=1;root<=n;root++){
		if(!dfn[root]){
			tarjan(root);
		}
	}
	printf("%llu",dcc_cnt);
	return 0;
}
求割点、割边(上述两者的 $\text{subset}$)

求割点

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dfn[N],low[N],n,res,idx,m,sum;
bool isc[N];
vector<int>G[N];
void dfs(int u,int fa){
	dfn[u]=low[u]=++idx;
	int child=0;
	for(int i=0;i<G[u].size();i++){
		int v=G[u][i];
		if(!dfn[v]){
			child++;
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]&&fa!=-1&&isc[u]==0)isc[u]=1,sum++;
		}
		else if(dfn[v]<dfn[u]&&v!=fa){
			low[u]=min(low[u],dfn[v]);//如果v在u访问它的祖先节点深搜时已经访问过,而不是直接通过u->v访问 
		}
	}
	if(fa==-1&&child>=2&&isc[u]==0){
		sum++;
		isc[u]=1;
		
	} 
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i])dfs(i,-1);
	}
	printf("%d\n",sum);
	for(int i=1;i<=n;i++){
		if(isc[i])printf("%d ",i);
	}
	return 0;
}

求割边(桥)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dfn[N],low[N],n,m,res,timer,idx;
bool isc[N];
vector<int>G[N];
struct node{
	int l;
	int r;
}edges[N];
bool cmp(node x,node y){
	if(x.l==y.l)return x.r<y.r;
	return x.l<y.l;
}
void dfs(int u,int fa){
	dfn[u]=low[u]=++timer;
	for(int i=0;i<G[u].size();i++){
		int v=G[u][i];
		if(!dfn[v]){
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				edges[++idx].l=min(u,v);
				edges[idx].r=max(u,v);
				isc[v]=1;
			}
		}
		else if(dfn[v]<dfn[u]&&v!=fa){
			low[u]=min(low[u],dfn[v]);
		}
	}
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
    	int a,b;
    	scanf("%d%d",&a,&b);
    	G[a].push_back(b);
    	G[b].push_back(a);
    }
    res=0;
    dfs(1,1);
    sort(edges+1,edges+idx+1,cmp);
    for(int i=1;i<=idx;i++){
    	printf("%d %d\n",edges[i].l,edges[i].r);
    }
    return 0;
}

求欧拉路径:

void dfs(int x){
	for(int i=del[x];i<G[x].size();i=del[x]){
		del[x]=i+1;
		dfs(G[x][i]);
	}
	stk[++tp]=x;
}
posted @ 2025-04-24 14:55  TBSF_0207  阅读(54)  评论(0)    收藏  举报