SCC+EDCC+VDCC 代码
有向图
SCC(强连通分量)计数
SCC 缩点后容易在新图中出现重边,这在某些题来说会导致错误的结果。一般来说我们可以如下处理:
显然首先对于 SCC 内连边是肯定不需要在新图中连出的。
-
使用
map<int,int>记录 \((u,v)\) 是否出现过,连边 \(O(n\log n)\)。 -
先不去重地连边,然后依次找第 \(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;
}

浙公网安备 33010602011771号