边联通:去掉这个边后,还是可以连通的
点联通: 去掉这个点后,还是可以连通的
目的: 就是找环,并且能把他们标记。
dfs+queue的思想(L版本)
tarjain 算法(可能打错了,欸嘿)
注意:
- 经常和 缩点联系在一起
- 入度出度的思想
- 拓扑排序等等
- 这zhuliu算法有相关的
边连通:
void tj(int a) { low[a]=dfn[a]=++cur; qu[++r]=a;vis[a]=1; for(ri i=0;i<p[a].size();i++) { int b=p[a][i]; if(!dfn[b]) { dfs(b); low[a]=min(low[a],low[b]); } else if(vis[b]) low[a]=min(dfn[b],low[a]); // 返祖边,有向边,要在vis还存在的时候,不然他不存在,说明人家不会到你这边来 } if(low[a]==dfn[a]) { cnt++; while(r>=1) { int tmp=qu[r--]; id[tmp]=cnt;vis[tmp]=0; // important, 把vis去掉 if(tmp==a) break; } } }
void tj(int a,int f) { low[a]=dfn[a]=++cur; qu[++r]=a; for(ri i=0;i<p[a].size();i++) { int b=p[a][i]; if(!dfn[b]) { dfs(b,a); low[a]=min(low[a],low[b]); } else if(b!=f) low[a]=min(dfn[b],low[a]); // 返祖边,无向边,一定不要是父亲,(这个时候一般都是告诉了你没有重边) } if(low[a]==dfn[a]) { cnt++; while(r>=1) { int tmp=qu[r--]; id[tmp]=cnt; if(tmp==a) break; } } }
#include <bits/stdc++.h> using namespace std; #define ri register int #define M 2000005 int n,m; vector <int>p[M]; int dfn[M],low[M],cur; int qu[M],r; vector<int>pp[M]; int cnt=0; void tj(int a,int f) { low[a]=dfn[a]=++cur; qu[++r]=a; int tmp=0; for(ri i=0;i<p[a].size();i++) { int b=p[a][i]; if(dfn[b]==0) { tj(b,a); low[a]=min(low[b],low[a]); } else if(b!=f||(b==f&&tmp)) low[a]=min(low[a],dfn[b]); if(b==f) tmp++; } if(low[a]==dfn[a]) { cnt++; while(r>=1) { int b=qu[r--]; pp[cnt].push_back(b); if(b==a) break; } } } int main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>n>>m; for(ri i=1;i<=m;i++) { int a,b; cin>>a>>b; p[a].push_back(b); p[b].push_back(a); } for(ri i=1;i<=n;i++) if(dfn[i]==0) tj(i,0); cout<<cnt<<"\n"; for(ri i=1;i<=cnt;i++) { cout<<pp[i].size()<<" "; for(ri j=0;j<pp[i].size();j++) { cout<<pp[i][j]<<" "; } cout<<"\n"; } return 0; }
求割点:
- 割点: 删除这个点后, 连通块的数量会增加
- low[b]>dfn[a], 不是根节点,或者是根节点,但是有2个环或者链,
#include <bits/stdc++.h> using namespace std; #define ri register int #define M 2000005 int n,m; vector <int> p[M]; int low[M],dfn[M],cur; int ge[M]; int root; int ans=0; void tj(int a,int f){ low[a]=dfn[a]=++cur; int cc=0,cc2=0; for(ri i=0;i<p[a].size();i++) { int b=p[a][i]; if(dfn[b]==0) { tj(b,a); low[a]=min(low[b],low[a]); if(ge[a]) continue; if(low[b]>=dfn[a]) { cc++; if(a!=root||cc>=2) ge[a]=1,ans++; } }else if(a!=f||cc2)) low[a]=min(dfn[b],low[a]); if(b==f) cc2++; } } int main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>n>>m; for(ri i=1;i<=m;i++) { int a,b; cin>>a>>b; p[a].push_back(b); p[b].push_back(a); } for(ri i=1;i<=n;i++) { if(dfn[i]==0) { root=i; tj(i,0); } } cout<<ans<<"\n"; for(ri i=1;i<=n;i++) { if(ge[i]) cout<<i<<" "; } return 0; }
注意有向边和无向边的区别
- 无向: 儿子不能是父亲
- 有向: 3个vis
应用:
- 无向图 缩完点之后就是一个 树!!
后记:
- 遇到图论题 一定要看他是不是连通的
- for(----) if() tj(i);
例题:
题目描述 JYY 最近痴迷于图的强连通性,所以对于任何有向图,JYY 都希望增加一些边使得这个图变成强连通图。JYY现在得到了一个 nn 个点 mm 条边的有向图,所有点从 11 到 nn 编号。 JYY 想知道: 在给定的图中,最多能选出多少个点,使得这些点在原图中两两可达? 在给定的图中,最少增加多少条边,可以使得这个图变成强连通图? 其中,一个有向图 G(V,E)G(V,E)是强连通的,当且仅当任意顶点 a,b\in V,a\neq ba,b∈V,a =b之间都存在 a\to ba→b 和 b\to ab→a 的路径。 输入格式 第一行包含两个整数 nn 和 mm。 接下来 mm 行,每行两个整数 xx 和 yy,表示图中有一条从 xx 到 yy 的有向边。 输出格式 两行,第一行表示第一个问题的答案,第二行表示第二个问题的答案。 输入输出样例 输入 #1复制 4 3 1 4 2 3 2 4 输出 #1复制 1 2 说明/提示 样例解释 1 对于第一个问题,无法选出互相连通两个点,答案为 11。 对于第二个问题,一种加边数最小的方案为 (3,1)(3,1) 和 (4,2)(4,2),答案为 22。 数据范围 对于 100\%100% 的数据,1\leq n\leq 10^5,1\leq m\leq 3\times 10^51≤n≤10 5 ,1≤m≤3×10 5 。
#include <bits/stdc++.h> using namespace std; #define ri register int #define M 100005 template <class G > void read(G &x) { x=0;int f=0;char ch=getchar(); while(ch<'0'||ch>'9'){f|=ch=='-';ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?-x:x; return ; } int n,m; vector <int> p[M]; int low[M],dfn[M],vis[M]; int flag[M],tot; int cent; int q[M];int ans; int l; void tj(int a) { dfn[a]=low[a]=++cent; q[++l]=a; vis[a]=1; for(ri i=0;i<p[a].size();i++) { int b=p[a][i]; if(!dfn[b]) { tj(b); low[a]=min(low[a],low[b]); } else if(vis[b]) low[a]=min(low[a],dfn[b]); } if(low[a]==dfn[a]) { tot++;int tmp=0; while(l>=1) { int b=q[l];l--; vis[b]=0; flag[b]=tot;tmp++; if(b==a) break; } ans=max(ans,tmp); } } int in[M],out[M]; int main(){ read(n);read(m); for(ri i=1;i<=m;i++) { int a,b;read(a);read(b); p[a].push_back(b); } for(ri i=1;i<=n;i++) // attention { if(!dfn[i]) tj(i); } for(ri i=1;i<=n;i++) { for(ri j=0;j<p[i].size();j++) { int b=p[i][j]; if(flag[i]==flag[b]) continue; out[flag[i]]++;in[flag[b]]++; } } int IN=0,OUT=0; for(ri i=1;i<=tot;i++) { if(out[i]==0) OUT++; if(in[i]==0) IN++; } printf("%d\n",ans); ans=max(OUT,IN); printf("%d",ans); return 0; }