tarjan全家桶
首先是tarjan家族的祖传操作:
维护两个数组:
- dfn:进入该点的时间戳
- low:能追溯到的最小时间
通用板子(不同用途会有细微不同):
inl void tarjan(int x){
dfn[x]=low[x]=++dfs_clock;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}else low[x]=min(low[x],dfn[y]);
}
}
缩点
每次找到一个新的点 我们把他压入栈中
对于一个点 如果它的 \(low\) 数组小于 \(dfn\) 那么它一定可以到达dfs搜索树上方某点 那么加上上方的点答案会更大
相反 如果 \(dfn=low\) 那么它和下方能到达它的点合起来答案最大 那么弹出栈直到弹出当前点 \(x\) 这些点就是一个强联通分量
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define mid (l+r>>1)
const int N=1e5+5;
const int M=1e5+5;
const int mod=1e5+3;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writel(int x){write(x);putchar('\n');}
int n,m,a[N],id,idx[N],sum[N],u[N],v[N],f[N],ans;
int dfn[N],low[N],vis[N],st[N],top,dfs_clock;
int head[N],nxt[N],to[N],cnt;
inl void add(int u,int v){
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
inl void tarjan(int x){
dfn[x]=low[x]=++dfs_clock;
st[++top]=x;vis[x]=1;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}else if(vis[y]){
low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]^low[x])return;id++;
while(1){
int y=st[top--];vis[y]=0;
idx[y]=id;sum[id]+=a[y];
if(x==y)break;
}
}
signed main(){
n=read();m=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=m;i++){
u[i]=read(),v[i]=read();
add(u[i],v[i]);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
memset(head,0,sizeof head);
memset(nxt,0,sizeof nxt);
cnt=0;
for(int i=1;i<=m;i++){
if(idx[u[i]]==idx[v[i]])continue;
add(idx[u[i]],idx[v[i]]);
}
for(int i=1;i<=id;i++)f[i]=sum[i];
for(int x=id;x;x--){
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
f[y]=max(f[y],f[x]+sum[y]);
}
}
for(int i=1;i<=id;i++)ans=max(ans,f[i]);
writel(ans);
return 0;
}
割点
一个点是割点 当且仅当:
- 它是根&&儿子数 \(\ge 2\)
- 它不是根&& \(low_y>=dfn_x\)
对于第一条 如果它是根且有两儿子以上 那么这些儿子只能从根互相联通 所以根是割点
对于第二条 如果它不是根&& \(low[y]>=dfn[x]\) 意味着儿子 \(y\) 无法到达 \(x\) 上面或之前的点 那么 \(x\) 一定是割点
这样说的前提条件:
else low[x]=min(low[x],dfn[y]);
如果这样写:
else low[x]=min(low[x],low[y]);
\(y\) 可能会取到 \(low_x\) 我们要判 \(y\) 能否到达 \(x\) 上面或之前的点 那么答案一定不对
(对于缩点 上面写法是对的 但点双边双一定会错 建议都写上边的一种)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define mid (l+r>>1)
const int N=2e5+5;
const int M=1e5+5;
const int mod=1e5+3;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,m,a[N],u,v,ans[N],num;
int dfn[N],low[N],dfs_clock;
int head[N],nxt[N],to[N],cnt;
inl void add(int u,int v){
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
inl void tarjan(int x,int flag){
dfn[x]=low[x]=++dfs_clock;
int child=0;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y,0);child++;
if(flag&&child>=2)ans[x]=1;
if(!flag&&low[y]>=dfn[x])ans[x]=1;
low[x]=min(low[x],low[y]);
}else low[x]=min(low[x],dfn[y]);
}
}
signed main(){
n=read();m=read();
for(int i=1;i<=m;i++){
u=read(),v=read();
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,1);
for(int i=1;i<=n;i++)if(ans[i])num++;
writel(num);
for(int i=1;i<=n;i++)if(ans[i])writei(i);
return 0;
}
点双
点双连通:若对于一个无向图,其任意一个节点对于这个图本身而言都不是割点,则称其点双连通。也就是说,删除任意点及其相关边后,整个图仍然属于一个连通分量。
点双连通分量:无向图中,极大的点双连通子图。与连通分量类似,抽离出一些点及它们之间的边,使得抽离出的图是一个点双连通图,在这个前提下,使得抽离出的图越大越好。
根据我们会发现:两个点双最多只有一个公共点(即都有边与之相连的点);且这个点在这两个点双和它形成的子图中是割点。
那么我们还是用栈存 找到割点/树根开始弹栈 但由于一个割点/树根可能属于多个点双 我们可以弹到它的儿子 然后单独把割点加上
弹出来的就是一个点双 注意要特判自环和独立点(无边相连的点)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=4e6+5;
const int M=1e5+5;
const int mod=1e5+3;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,m,a[N],u,v,num,id,st[N],top;
int dfn[N],low[N],dfs_clock;
int head[N],nxt[N],to[N],cnt;
vector<int>ve[N];
inl void add(int u,int v){
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
inl void tarjan(int x,int flag){
dfn[x]=low[x]=++dfs_clock;
st[++top]=x;
if(flag&&!head[x])return ve[++num].push_back(x),void();
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y,0);
if(low[y]>=dfn[x]){
num++;
while(1){
int p=st[top--];
ve[num].push_back(p);
if(p==y)break;
}
ve[num].push_back(x);
}
low[x]=min(low[x],low[y]);
}else low[x]=min(low[x],dfn[y]);
}
}
signed main(){
n=read();m=read();
for(int i=1;i<=m;i++){
u=read(),v=read();
if(u==v)continue;
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,1);
writel(num);
for(int i=1;i<=num;i++){
writei(ve[i].size());
for(auto x:ve[i])writei(x);puts("");
}
return 0;
}
边双
还是先看割边
一条边是割边 当且仅当 $$low_y>dfn_x$$
很好理解 如果 \(low_y<dfn_x\) 那么这两个点只有这一条边相连 割掉这条边两个点就必然不联通 否则证明 \(y\) 有另一条边指向 \(x\) 或之前的点
边双定义与点双类似 但由于割边必然不属于任意一个边双 那么我们可以一遍tarjan求割边 再dfs一遍即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=4e6+5;
const int M=1e5+5;
const int mod=1e5+3;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,m,a[N],u,v,num,id,st[N],top,gb[N],vis[N];
int dfn[N],low[N],dfs_clock;
int head[N],nxt[N],to[N],cnt=1;
vector<int>ve[N];
inl void add(int u,int v){
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
inl void tarjan(int x,int fa){
dfn[x]=low[x]=++dfs_clock;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y,x);
if(low[y]>dfn[x])gb[i]=gb[i^1]=1;
low[x]=min(low[x],low[y]);
}else if(y^fa)low[x]=min(low[x],dfn[y]);
}
}
inl void dfs(int x,int num){
vis[x]=1;
ve[num].push_back(x);
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(vis[y]||gb[i])continue;
dfs(y,num);
}
}
signed main(){
n=read();m=read();
for(int i=1;i<=m;i++){
u=read(),v=read();
if(u==v)continue;
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
for(int i=1;i<=n;i++)if(!vis[i])dfs(i,++num);
writel(num);
for(int i=1;i<=num;i++){
writei(ve[i].size());
for(auto x:ve[i])writei(x);puts("");
}
return 0;
}

浙公网安备 33010602011771号