首先复习下知识点
定义:
SCC:强连通分量
BCC:连通分量
割点:图删除该点与该点的边后连通分量数+1 判断:儿子的low>=自己的tin
割边/桥:图删除该边后连通分量数+1 判断 :儿子的low>自己的tin
(割点与割边只在无向图内讨论)
强连通分量:任意两点均可互达
点双连通分量:无割点的连通分量
边双连通分量:无割边的连通分量
强连通分量可以缩为一个点用来解决问题
需要学会的是 缩点,求割边,求割点,求点双,边双
tin[]第一次访问的时间戳数组
low[]能访问到的最小的tin值
belong[]下标属于哪个点双
第一个板子样例 缩点+强连通分量个数(有向图)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int n,m;
stack<int>s;
int tin[N],low[N],now_time,scc_cnt,belong[N];
vector<int>son[N];
int f[N],du[N],fff[N];
void dfs(int u,int fa)
{
s.push(u);
low[u]=tin[u]=++now_time;
for(auto v: son[u])
{
if(!tin[v])
{
dfs(v,u);
low[u]=min(low[u],low[v]);
}
if(!belong[v])
low[u]=min(low[u],tin[v]);
}
if(tin[u]==low[u])
{
scc_cnt++;
int now;
while(1)
{
now=s.top();s.pop();
belong[now]=scc_cnt;
if(now==u) break;
}
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>m&&n+m)
{
while(s.size()) s.pop();
for(int i=1;i<=n;i++) son[i].clear(),scc_cnt=now_time=0,tin[i]=low[i]=0,belong[i]=0,du[i]=0;
for(int i=1;i<=n;i++) cin>>f[i];
for(int i=1;i<=n;i++) fff[i]=1e8;
for(int i=1;i<=m;i++)
{
int a1,a2;
cin>>a1>>a2;
son[a1].push_back(a2);
//son[a2].push_back(a1);
}
for(int i=1;i<=n;i++)
if(!tin[i])
dfs(i,0);
//if(scc_cnt==1) cout<<"Yes"<<endl;
// else cout<<"No"<<endl;
for(int i=1;i<=n;i++)
fff[belong[i]]=min(fff[belong[i]],f[i]);
for(int i=1;i<=n;i++)
{
for(int j=0;j<son[i].size();j++)
{
if(belong[i]!=belong[son[i][j]])
du[belong[son[i][j]]]++;
}
}
int cnt=0,sum=0;
for(int i=1;i<=scc_cnt;i++)
{
if(du[i]==0)
cnt++,sum+=fff[i];
}
cout<<cnt<<' '<<sum<<endl;
//cout<<scc_cnt<<endl;
//for(int i=1;i<=n;i++)
// cout<<i<<' '<<tin[i]<<' '<<low[i]<<' '<<belong[i]<<' '<<endl;
}
}
有向图的缩点为low值一样的为一个点
第二个板子 无向图求点双连通分量:例题 (求在简单环上边的个数)
#include<bits/stdc++.h>
#define fi first
#define se second
#define P pair<int ,int>
#define PII pair< pair<int ,int >,int>
using namespace std;
typedef long long ll;
const int N=1e6+7;
vector<P>son[N];
vector<int>bcc_node[N],bcc_edge[N];///存连通分量的点和边
stack<PII>s;
int bcc_cnt,now_time,tin[N],low[N],belong[N],is_cnt[N];
void dfs(int u,int fa)
{
tin[u]=low[u]=++now_time;
int child=0;
for(int i=0;i<son[u].size();i++)
{
int v=son[u][i].first;
if(v==fa) continue;
PII edge={{u,v},son[u][i].second};
if(!tin[v])
{
s.push(edge);
dfs(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=tin[u])
{
child++;
is_cnt[u]=1;
bcc_cnt++;
while(1)
{
PII now=s.top();s.pop();
bcc_edge[bcc_cnt].push_back(now.se);
if(belong[now.fi.fi]!=bcc_cnt)
belong[now.fi.fi]=bcc_cnt,bcc_node[bcc_cnt].push_back(now.fi.fi);
if(belong[now.fi.se]!=bcc_cnt)
belong[now.fi.se]=bcc_cnt,bcc_node[bcc_cnt].push_back(now.fi.se);
if(now.fi.fi==u&&now.fi.se==v) break;
}
}
}
else if(tin[v]<tin[u])
{
s.push(edge);
low[u]=min(tin[v],low[u]);
}
}
}
int main()
{
int n,m,u,v;
cin>>n>>m;
vector<int>ans;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
son[u].push_back({v,i});
son[v].push_back({u,i});
}
for(int i=1;i<=n;i++)
dfs(i,i);
for(int i=1;i<=bcc_cnt;i++)
if(bcc_node[i].size()==bcc_edge[i].size())
{
for(auto v : bcc_edge[i])
ans.push_back(v);
}
cout<< ans.size()<<endl;
sort( ans.begin(), ans.end());
for(int i=0;i< ans.size();i++)
cout<< ans[i]<<' ';
cout<<endl;
return 0;
}
第三个板子 求边双
好吧!没有板子 边双就是无割边的连通分量,割边就是不走父边,如果一个儿子的low大于自己的tin,这边就是割边,这个差不多的将就着用吧
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
vector<int>son[N];
int belong[N], tin[N], low[N], now_time, scc_cnt;
stack<int>s;
void dfs(int o,int fa) {
tin[o] = low[o] = ++now_time;
s.push(o);
for (auto v : son[o]) {
if(v==fa) continue;
if (!tin[v]) dfs(v,o);
if(!belong[v])
low[o] = min(low[o], low[v]);
}
if (tin[o] == low[o]) {
scc_cnt++;
int now;
while (true) {
now = s.top();
s.pop();
belong[now] = scc_cnt;
low[now]=min(low[now],low[o]);
if (now == o) break;
}
}
}
///hand hands,loud lows
int main()
{
int n, m;
cin>>n>>m;
while (m--)
{
int a1, a2;
cin >> a1 >> a2;
son[a1].push_back(a2);
son[a2].push_back(a1);
}
dfs(1,-1);
for(int i=1;i<=n;i++) cout<<tin[i]<<' '<<low[i]<<' '<<belong[i]<<endl;
cout<<scc_cnt<<endl;
}