无向图的连通性
割点 桥
#include<iostream>
#include<stdio.h>
#include<set>
#include<cstdio>
#include<string.h>
#include<cstdlib>
#include<stack>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<bitset>
#include<list>
#include<sstream>
#include<map>
#include<functional>
using namespace std;
#define fi first
#define se second
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int N=103;
int ans,dfn,low[N],num[N];
bool iscut[N];
vector<int>G[N];
void init(int n){
ans=dfn=0;
memset(low,0,sizeof(low));
memset(num,0,sizeof(num));
memset(iscut,0,sizeof(iscut));
FOR(i,1,n) G[i].clear();
}
void dfs(int u,int fa){
low[u]=++dfn;num[u]=dfn;
int child=0;
for(int v=0;v<G[u].size();++v){ //用auto会CE
int i=G[u][v];
if(!num[i]){
child++,dfs(i,u),low[u]=min(low[u],low[i]);
if(low[i]>=num[u]&&u!=1) iscut[u]=1;
}else if(num[i]<num[u]&&i!=fa) low[u]=min(low[u],num[i]);
}
if(u==1&&child>=2) iscut[u]=1;
}
int main(){
int n;
while(~scanf("%d",&n)&&n){
init(n);
int u,v;char ch;
while(~scanf("%d",&u)&&u){
ch=getchar();
while(ch!='\n'){
scanf("%d",&v);
G[u].pb(v);G[v].pb(u);
ch=getchar();
}
}
dfs(1,-1);
FOR(i,1,n) if(iscut[i]) ans++;
printf("%d\n",ans);
}
}
双连通分量
POJ1523 SPF 判断割点+计算每个割点所分的点双连通分量个数
#include <cstdio>
#include <vector>
#include <string.h>
using namespace std;
const int N = 1e3+4;
int x,y,cnt,low[N],num[N],dfn,bcc[N];
bool iscut[N];
int rt=1;
vector<int>G[N];
void dfs(int u,int fa){
bcc[u]=1;
low[u]=num[u] = ++dfn;
int child = 0;
for(int i=0;i<G[u].size();++i){
int v = G[u][i];
if(!num[v]){
child++;
dfs(v,u);
low[u] = min(low[u],low[v]);
if(low[v]>=num[u]&&u!=rt) {
bcc[u]++;
iscut[u]=1;
}
}else if(num[v]<num[u]&&v!=fa) low[u] = min(low[u],num[v]);
}
if(u==rt&&child>=2) {
bcc[u] = child;
iscut[1]=1;
}
}
int main(){
int c=0;
while(~scanf("%d",&x)&&x){
for(int i=1;i<N;++i) G[i].clear();
memset(iscut,0,sizeof(iscut));
memset(low,0,sizeof(low));
memset(num,0,sizeof(num));
dfn=0;
cnt=0;
scanf("%d",&y);
G[x].push_back(y);
G[y].push_back(x);
while(~scanf("%d",&x)&&x){
scanf("%d",&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1,-1);
for(int i=1;i<N;++i) if(iscut[i]) cnt++;
printf("Network #%d\n",++c);
if(cnt==0) {puts(" No SPF nodes\n");continue;}
for(int i=1;i<=N;++i) if(iscut[i]){
printf(" SPF node %d leaves %d subnets\n",i,bcc[i]);
}
puts("");
}
}
POJ3352 Road Construction 边双连通分量
#include <cstdio>
#include <string.h>
#include <vector>
#include <iostream>
int n,r;
using namespace std;
const int N = 1e3+5;
int low[N],dfn;
vector<int> G[N];
int ans;
void init(){
for(int i=1;i<=n;++i) G[i].clear();
memset(low,0,sizeof(low));
ans = 0;dfn=0;
}
void dfs(int u,int fa){
low[u]=++dfn;
for(int i=0;i<G[u].size();++i){
int v = G[u][i];
if(v==fa) continue;
if(!low[v]) dfs(v,u);
low[u] = min(low[u],low[v]);
}
}
void tarjan(){
int degree[N];
memset(degree,0,sizeof(degree));
for(int i=1;i<=n;++i)
for(int j=0;j<G[i].size();++j)
if(low[i]!=low[G[i][j]]) degree[low[i]]++;
for(int i=1;i<=n;++i) if(degree[i]==1) ans++;
ans=(ans+1)/2;
}
int main(){
string str;
int c = 0;
while(~scanf("%d%d",&n,&r)){
init();
for(int i=1;i<=r;++i){
int x,y;scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1,-1);
tarjan();
printf("%d\n",ans);
}
}
/*
* 分析可知,
* no need to build railways 必然是 bridge
* 而 railways where clash might happen 必然存在于 点双连通分量 之中。
* 为什么?因为BELONGS TO MORE THAN ONE TOURIST ROUTES。
* 即,这条边是两个环的公共边。
* 那么,这两个环构成一个BCC,(?)也构成一个边双连通分量啊?
* 且,这个BCC的所有边都为railways where clash might happen。
* 那么,反过来,怎样的BCC满足条件呢?
* 发现,是边数目大于点数目的BCC满足条件。
* BCC只有两种:(1)边数目 == 点数目(即简单环);(2)边数目大于点数目。
* 只用寻找所有BCC中,除开简单环的BCC的边的数目。
* 找BCC使用tarjan算法。
*
*/
#include <iostream>
#include <string.h>
#include <algorithm>
#include <vector>
#include <stack>
const int N = 1e4+5;
const int M = 2e5+6;
using namespace std;
int n,m,dfn;
int low[N],num[N],instack[N],inbcc[N];
vector<int>G[N];
stack<int>s;
vector<int>bcc;
int ans,ans2;
void solve(){
// 处理当前得到的bcc。
memset(inbcc,0,sizeof(inbcc));
for(int i=0;i<bcc.size();++i) inbcc[bcc[i]] = 1;
int cnt = 0;
for(int i=0;i<bcc.size();++i){
int u = bcc[i];
for(int j=0;j<G[u].size();++j){
int v = G[u][j]; // 处理bcc中所有点的所有的边
if(inbcc[v]) cnt++; // 在这个bcc内部的边
}
}
cnt/=2; // 两个点之间只有一条边,之前进行了两次运算
if(cnt>bcc.size()) ans2+=cnt;
}
// 奇怪,没有考虑起点是割点的情况?
int tarjan(int u,int fa){
// 记录边数
low[u] = num[u] = ++dfn;
s.push(u); instack[u] = 1;
// 栈里放的是点
for(int i=0;i<G[u].size();++i){
int v = G[u][i];
if(v==fa) continue;
if(!low[v]) {
tarjan(v,u);
low[u] = min(low[u],low[v]);
if(low[v]>num[u]) ans++; // 找到桥
if(low[v]>=num[u]) { // 找到割点,意即找到了BCC
bcc.clear(); // 先清空
while(1){
int x = s.top();s.pop(),instack[x] = 0;
bcc.push_back(x);
if(x==v) break;
}
bcc.push_back(u);
solve(); // 处理当前BCC
}
}else if(instack[v]) low[u] = min(low[u],num[v]); // 回退边
}
}
void init(){
memset(num,0,sizeof(num));
memset(low,0,sizeof(low));
memset(instack,0,sizeof(instack));
dfn = ans = ans2 = 0;
while(!s.empty()) s.pop();
for(int i=0;i<=n;++i) G[i].clear();
}
int main() {
while(~scanf("%d%d",&n,&m),n||m){
init();
for(int i=1;i<=m;++i){
int u,v;scanf("%d%d",&u,&v);
G[u].push_back(v),G[v].push_back(u);
}
// 把每一个连通块都dfs一遍。
for(int i=0;i<n;++i) if(!num[i]) tarjan(i,-1);
printf("%d %d\n",ans,ans2);
}
}
HDU3749 Financial Crisis 判断连通性+点双连通分量
#include<bits/stdc++.h>
using namespace std;
const int N = 5004;
int n,m,q;
vector<int>G[N];
/*
* 从每个点出发查找所在BCC,如果没有另一个点,但可达,即1.如果不可达,0.如果构成bcc,2
* 这样显然会TLE. 于是我们考虑一次tarjan,储存每个点属于的bcc。 暴力u的bcc和v的bcc,如果有一样的,再判断这个bcc的size。
* 连通性用并查集判断。
*/
int cnt; // bcc 的编号
int dfn,low[N],num[N],fa[N],instk[N];
stack<int>s;
vector<int>bcc[N],in[N];
// 每一个点在哪几个bcc之中
// bcc有编号
void init(){
while(!s.empty()) s.pop();
cnt=dfn=0;
for(int i=0;i<=n;++i)
G[i].clear(),bcc[i].clear(),in[i].clear(),fa[i]=i,num[i]=0,low[i]=0,instk[i]=0;
}
int find(int x){
return fa[x]=(fa[x]==x)?x:find(fa[x]);
}
void unite(int x,int y){
x = find(x),y = find(y);
if(x==y) return;
fa[x] = y;
}
void tarjan(int u,int fa){
low[u] = num[u] = ++dfn;
s.push(u); instk[u] = 1;
for(auto v:G[u]){
if(v==fa) continue;
if(!low[v]){
tarjan(v,u);
low[u] = min(low[u],low[v]);
if(low[v]>=num[u]){
cnt++;
while(1){
int x = s.top();s.pop();instk[x] = 0;
bcc[cnt].push_back(x);
in[x].push_back(cnt);
if(x==v) break;
}
bcc[cnt].push_back(u);
in[u].push_back(cnt);
}
}else if(instk[v]) low[u] = min(low[u],num[v]);
}
}
int main(){
int c = 0;
while(~scanf("%d%d%d",&n,&m,&q),(n||m||q)){
init();
for(int i=1;i<=m;++i){
int v,u;scanf("%d%d",&u,&v);
unite(u,v);
G[u].push_back(v),G[v].push_back(u);
}
for(int i=0;i<n;++i) if(i==fa[i]) tarjan(i,-1);
printf("Case %d:\n",++c);
for(int i=1;i<=q;++i){
int u,v;scanf("%d%d",&u,&v);
if(find(u)!=find(v)) puts("zero");
else {
int flg=0;
for(auto i:in[u]){
for(auto j:in[v]){
if(i==j&&bcc[i].size()>2) {puts("two or more");flg=1;break;}
}
if(flg) break;
}
if(!flg) puts("one");
}
}
}
}
HDU2460 Network 加边,多次询问桥的数目
给一张连通无向图。q次加边,求每次加边之后的桥的数目。
- 思路:题目的意思是要求在原图中加边后桥的数量,首先我们可以通过Tarjan求边双连通分量,对于边(u,v),如果满足
low[v]>dfn[u],则为桥,这样我们就可以知道图中桥的数目了。对于每一次query,可以考虑dfs树,树边肯定是桥,然后连
上u,v这条边之后,就会形成一个环,这样环内的边就不是割边了,所以只要找到u,v的LCA,把这个路径上的桥标记为否就可以了。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+6;
int num[N],low[N],f[N],dfn,bridges,m,n,q,isbridge[N],mark[N];
vector<int>G[N]; // is bridge
void tarjan(int u,int fa){
num[u] = low[u] = ++dfn;
int first = 1;
for(auto v:G[u]){
if(v==fa&&first) {first = 0;continue;} // multiple edges between two nodes
if(!low[v]) f[v]=u,tarjan(v,u);
low[u] = min(low[v],low[u]);
if(low[v]>num[u]) {
if(!isbridge[v]) bridges++;
isbridge[v] = 1; // tree edge is bridge
}
}
}
void lca(int a,int b){
while(num[a]>num[b]) {
if(isbridge[a]) bridges--;
isbridge[a] = 0,a=f[a];
}
while(num[b]>num[a]) {
if(isbridge[b]) bridges--;
isbridge[b] = 0,b=f[b];
}
while(a!=b) {
if(isbridge[a]) bridges--;
if(isbridge[b]) bridges--;
isbridge[a] = isbridge[b] = 0;
a=f[a],b=f[b];
}
}
void init(){
for(int i=1;i<=n;++i) isbridge[i]=low[i]=0,G[i].clear(),f[i]=i,mark[i]=0,num[i]=-1;
bridges = 0,dfn = 0;
}
int main(){
int c = 0;
while(~scanf("%d%d",&n,&m),n||m){
init();
printf("Case %d:\n",++c);
for(int i=1;i<=m;++i){
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
tarjan(1,-1); // any two computers are connected in the initial network
scanf("%d",&q);
while(q--){
int a,b;scanf("%d%d",&a,&b);
lca(a,b);
printf("%d\n",bridges);
}
printf("\n");
}
}
HDU 4597 TWO NODES 无向图删两个点求最大连通分量数
思路:暴力枚举第一个删掉的点,重新建图,再对新图进行tarjan求每个点删去后的连通分量,注意,答案要加上删掉第一个点后图中的连通分量数目-1。
//
// Created by Lenovo on 2021/1/20.
//
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int maxn = 5004;
int low[maxn],num[maxn],dfn,bcc[maxn],edgex[maxn],edgey[maxn];
bitset<maxn> iscut;
int rt;
vector<int> G[maxn];
void dfs(int u,int fa){
bcc[u]=1;
low[u]=num[u]=++dfn;
int child = 0;
for(int i=0;i<G[u].size();++i){
int v=G[u][i];
if(!num[v]){
child++;
dfs(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=num[u]&&u!=rt)
bcc[u]++,iscut[u]=1;
}else if(num[v]<num[u]&&v!=fa) low[u]=min(low[u],num[v]);
}
if(u==rt&&child>=2) bcc[u]=child,iscut[1]=1;
}
void init(){
for(int i=1;i<=n;++i) G[i].clear();
iscut.reset();
for(int i=1;i<=n;++i) num[i]=0;
for(int i=1;i<=n;++i) low[i]=0;
for(int i=1;i<=n;++i) bcc[i]=0;
dfn=0;
}
int main(){
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=m;++i){
scanf("%d%d",&edgex[i],&edgey[i]);
edgex[i]++,edgey[i]++;
}
for(int j=1;j<=m;++j){
if(edgex[j]==1||edgey[j]==1) continue;
G[edgex[j]].push_back(edgey[j]);
G[edgey[j]].push_back(edgex[j]);
}
int ans=0;
for(int i=1;i<=n;++i){
// 枚举第一个点
init();
for(int j=1;j<=m;++j){
// 重新建图
if(edgex[j]==i||edgey[j]==i) continue;
G[edgex[j]].push_back(edgey[j]);
G[edgey[j]].push_back(edgex[j]);
}
int cnt=0;
// 找bcc数目
for(int j=1;j<=n;++j){
if(j==i) continue;
if(num[j]) continue;
cnt++;
rt=j;
dfs(j,-1);
}
int cur=0;
for(int j=1;j<=n;++j)
if(iscut[j])
cur=max(bcc[j],cur);
// cout<<"i == "<<i<<" cur == "<<cur<<" cnt== "<<cnt<<endl;
ans=max(ans,cur+cnt-1);
}
if(ans==0) ans=1;
cout<<ans<<endl;
}
}
有向图的连通性
Kosaraju算法
求强连通分量的算法。用到了反图技术。
HDU 1269 迷宫城堡 求强连通分量是否=1
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10005;
// kosaraju
vector<int>G[maxn],rG[maxn];
vector<int>S; // 存一次dfs1的结果:标记点的先后顺序
int vis[maxn],sccno[maxn],cnt;
void dfs1(int u){
if(vis[u]) return;
vis[u]=1;
for(int i=0;i<G[u].size();++i) dfs1(G[u][i]);
S.push_back(u);
}
void dfs2(int u){
if(sccno[u]) return;
sccno[u]=cnt;
for(int i=0;i<rG[u].size();++i) dfs2(rG[u][i]);
}
void Kosaraju(int n){
cnt=0;
S.clear();
memset(sccno,0,sizeof(sccno));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i) dfs1(i); // 点的编号
for(int i=n-1;i>=0;--i) if(!sccno[S[i]]){cnt++;dfs2(S[i]);}
}
int main(){
int n,m,u,v;
while(scanf("%d%d",&n,&m),n!=0||m!=0){
for(int i=0;i<n;++i) {G[i].clear();rG[i].clear();}
for(int i=0;i<m;++i){
scanf("%d%d",&u,&v);
G[u].push_back(v); // 原图
rG[v].push_back(u); // 反图
}
Kosaraju(n);
printf("%s\n",cnt==1?"Yes":"No");
}
}
Tarjan算法
同上题
#include<bits/stdc++.h>
using namespace std;
const int N = 10003;
int cnt;
int low[N],num[N],dfn;
int sccno[N],top;
vector<int>G[N];
stack<int> s;
void dfs(int u){
s.push(u);
low[u]=num[u]=++dfn;
for(int i=0;i<G[u].size();++i){
int v=G[u][i];
if(!num[v]){
dfs(v);
low[u]=min(low[v],low[u]);
}
else if(!sccno[v]) low[u]=min(low[u],num[v]);
}
if(low[u]==num[u]){
cnt++;
while(1){
int v=s.top();
s.pop();
sccno[v]=cnt;
if(u==v) break;
}
}
}
void Tarjan(int n){
cnt=dfn=0;
while(!s.empty()) s.pop();
memset(sccno,0,sizeof(sccno));
memset(num,0,sizeof(num));
memset(low,0,sizeof(low));
for(int i=1;i<=n;++i) if(!num[i]) dfs(i);
}
int main(){
int n,m,u,v;
while(scanf("%d%d",&n,&m),n!=0||m!=0){
for(int i=1;i<=n;++i) G[i].clear();
for(int i=0;i<m;++i){
scanf("%d%d",&u,&v);
G[u].push_back(v);
}
Tarjan(n);
printf("%s",cnt==1?"Yes":"No");
}
}
HDU 1827 Summer Holiday tarjan缩点
tarjan算法自带缩点到一个个scc中,所以不用dsu。
分为一个个scc后,如果开始是连通图,那么现在便成为一棵树。
#include<bits/stdc++.h>
using namespace std;
// 先缩点,再找in为0的scc。
const int maxn = 2003;
int fee[maxn],cnt,dfn,low[maxn],num[maxn],sccno[maxn],mn[maxn];
vector<int> G[maxn];
stack<int> s;
void dfs(int u){
s.push(u);
low[u]=num[u]=++dfn;
for(int i=0;i<G[u].size();++i){
int v=G[u][i];
if(!num[v]){
dfs(v);
low[u]=min(low[v],low[u]);
}else if(!sccno[v])
low[u]=min(low[u],num[v]);
}
if(low[u]==num[u]){
cnt++;
while(1){
int v=s.top();s.pop();
sccno[v]=cnt;
mn[cnt]=min(mn[cnt],fee[v]);
if(u==v) break;
}
}
}
void Tarjan(int n){
cnt=dfn=0;
memset(sccno,0,sizeof(sccno));
memset(num,0,sizeof(num));
memset(mn,0x7f7f7f7f,sizeof(mn));
for(int i=1;i<=n;++i) if(!num[i]) dfs(i);
}
int in[maxn];
int ans,res;
void Toposort(int n){
memset(in,0,sizeof(in));
for(int i=1;i<=n;++i){
for(int j=0;j<G[i].size();++j){
int v=G[i][j];
int si=sccno[i],sv=sccno[v];
if(si==sv) continue;
in[sv]++;
}
}
ans=0,res=0;
for(int i=1;i<=cnt;++i) if(!in[i]){
ans+=mn[i];
res++;
}
}
int main(){
int n,m;
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1;i<=n;++i) scanf("%d",&fee[i]);
for(int i=1;i<=m;++i) {
int a,b;scanf("%d%d",&a,&b);
G[a].push_back(b);
}
Tarjan(n);
Toposort(n);
cout<<res<<" "<<ans<<endl;
}
}
HDU 3072 Intelligence System tarjan缩点+贪心
选最小cost的路径,走遍所有点。scc内部cost不计。
每个点一定会被访问到。那么选择每个点入度中最小cost的那条入度,父亲一定也会被访问到。
//
// Created by Artis on 2021/1/21.
//
#include<bits/stdc++.h>
using namespace std;
const int maxn = 50003;
vector<pair<int,int> >G[maxn];
int fee[maxn],cnt,dfn,low[maxn],num[maxn],sccno[maxn];
stack<int>s;
void dfs(int u){
s.push(u);
low[u]=num[u]=++dfn;
for(int i=0;i<G[u].size();++i){
int v=G[u][i].first;
if(!num[v]){
dfs(v);
low[u]=min(low[v],low[u]);
}else if(!sccno[v])
low[u]=min(low[u],num[v]);
}
if(low[u]==num[u]){
cnt++;
while(1){
int v=s.top();s.pop();
sccno[v]=cnt;
if(u==v) break;
}
}
}
void Tarjan(int n){
memset(num,0,sizeof(num));
memset(sccno,0,sizeof(sccno));
memset(fee,0x7f7f7f7f,sizeof(fee));
dfn=cnt=0;
dfs(1);
for(int i=1;i<=n;++i){
for(int j=0;j<G[i].size();++j){
int v=G[i][j].first,val=G[i][j].second;
if(sccno[i]==sccno[v]) continue;
fee[sccno[v]]=min(fee[sccno[v]],val);
}
}
int ans=0;
for(int i=1;i<=cnt;++i) {
if(i==sccno[1]) continue;
ans+=fee[i];
}
cout<<ans<<endl;
}
int main(){
int n,m;
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1;i<=m;++i){
int u,v,val;scanf("%d%d%d",&u,&v,&val);
G[++u].push_back(make_pair(++v,val));
}
Tarjan(n);
}
}