割点&&桥&&边双&&点双
定义:
割点:将原图中的某一点以及它所连的边删除后,原图不连通。
桥:将原图中的某一边删除后,原图不连通。
边双连通分量:原图中意删除一边后还连通的极大连通子图。
点双连通分量:原图中任意删除一点后还连通的极大连通子图。
求法:
割点:
考虑原图的 dfs 生成树,对于树边更新 :
关键点:假如对于
为什么呢?原因就在于
代码:
void tarjan(int u,int fa){ dfn[u]=low[u]=++tim; int child=0; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(!dfn[v]){ child++; tarjan(v,u); low[u]=min(low[u],low[v]); if(fa!=-1&&low[v]>=dfn[u]){ cut[u]=true; } } else if(dfn[v]<dfn[u]&&v!=fa){ low[u]=min(low[u],dfn[v]); } } if(fa==-1&&child>=2){ cut[u]=true;//这里注意 u 为根节点的情况 } return; }
桥:
还是一样,使用 tarjan 求解。
这里有一个性质:
证明同上。
代码:
#include<bits/stdc++.h> #define L long long using namespace std; const int mod=1000000007,N=1145; L pre[N],dfn[N],low[N],n,m,dt,ans; bool vis[N]; vector<L> G[N]; struct edge{ L from,to; }a[N]; inline int read() { int x=0,f=1;char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();} return x*f; } void dfs(L fa ,L now){ dfn[now]=low[now]=++dt; L len=G[now].size(); for(L i=0;i<len;i++){ L next=G[now][i]; if(next!=fa&&dfn[next]) low[now]=min(low[now],dfn[next]); if(!dfn[next]){ vis[now]=false; dfs(now,next); if(dfn[now]<low[next]){ a[++ans].from=min(now,next); a[ans].to=max(now,next); } low[now]=min(low[now],low[next]); } } } bool cmp(edge p,edge q){ if(p.from!=q.from) return p.from<q.from; return p.to<q.to; } int main(){ n=read(),m=read(); memset(vis,true,sizeof(vis)); for(L i=1;i<=m;i++){ L u=read(),v=read(); G[v].push_back(u); G[u].push_back(v); } for(L i=1;i<=n;i++) if(!dfn[i]) dfs(i,i); sort(a+1,a+ans+1,cmp); for(L i=1;i<=ans;i++){ printf("%lld %lld\n",a[i].from,a[i].to); } return 0; }
边双:
与强连通分量一样,当遍历完
代码如下:
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5+10; struct edge{ int v,next; }edges[N*10]; int head[N],idx=2; int dfn[N],low[N],clk; int st[N],top; bool Cut[N*10]; vector<int>ans[N]; int edcc; void add_edge(int u,int v){ idx++; edges[idx].v=v; edges[idx].next=head[u]; head[u]=idx; return; } void tarjan(int u,int fa){ dfn[u]=low[u]=++clk; st[++top]=u; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(v==fa)continue; if(!dfn[v]){ tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]){ Cut[i]=Cut[i^1]=true; } } else{ low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]){ edcc++; do{ ans[edcc].push_back(st[top]); }while(st[top--]!=u); } return; } signed main(){ std::ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n,m; cin>>n>>m; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x); } tarjan(1,-1); cout<<edcc<<endl; return 0; }
点双:
点双即是找到割点后,将割点上的所有元素出栈,并加入这个点双中(包括割点也要加入),但不要将割点出栈(因为有可能割点属于多个点双)。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10; struct edge{ int v,next; }edges[N]; int head[N],idx; int n,m; int low[N],dfn[N],clk; vector<int>ans[N]; int st[N],top; bool Cut[N]; int vdcc,root; void add_edge(int u,int v){ idx++; edges[idx].v=v; edges[idx].next=head[u]; head[u]=idx; return; } void tarjan(int u){ dfn[u]=low[u]=++clk; st[++top]=u; if(u==root&&(!head[u])){ vdcc++; ans[vdcc].push_back(u); return; } int child=0; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(!dfn[v]){ tarjan(v); child++; low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]){ if(u!=root||child>1)Cut[u]=true; vdcc++; int tmp; do{ tmp=st[top--]; ans[vdcc].push_back(tmp); }while(tmp!=v); ans[vdcc].push_back(u); } } else low[u]=min(low[u],dfn[v]); } return; } signed main(){ std::ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>n>>m; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; if(x==y)continue; add_edge(x,y); add_edge(y,x); } for(int i=1;i<=n;i++)if(!dfn[i]){ root=i; tarjan(i); } cout<<vdcc<<endl; return 0; }
本文作者:Little_corn
本文链接:https://www.cnblogs.com/little-corn/p/18157442
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步