强连通 割点 割边 视频:https://www.bilibili.com/video/av41752079/?p=1 (细节讲的很清楚了)
求强连通、缩点而言是有向图
求割点、割边而言是无向图
解释看代码
求强连通块:
习题:https://www.luogu.org/problemnew/show/P2921
代码:
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 1e5+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;
ll n,dfn[maxn],low[maxn]; //inf表示第几个访问该点,low表示当前点能返回的最早时间戳
vector<ll>G[maxn]; //存图
stack<ll>s;
bool instack[maxn]; //标记是否在栈中
ll timing; //时间戳
ll colorcnt; //强连通分量数量
ll color[maxn]; //第i个点属于哪个强连通分量 或者染成了哪种颜色
ll colornum[maxn]; //该强连通分量里有多少个点
ll ans[maxn];
void tarjan(ll now)
{
timing++;
dfn[now] = low[now] = timing;
s.push(now);
instack[now] = true;
for(ll i = 0; i < G[now].size(); i++)
{
ll to = G[now][i];
//cout << to << endl;
if(dfn[to] == 0) //如果未访问
{
tarjan(to);
low[now] = min(low[now],low[to]);
}
else if(instack[to] == true) //如果访问过且在栈中(属于同一强连通分量)
low[now] = min(low[now],dfn[to]);
}
if(low[now] == dfn[now]) //找到该连通分量的初始点
{
colorcnt++;
while( s.top() != now )
{
ll tmp = s.top();
s.pop();
instack[tmp] = false; //不要忘了清楚标记
color[tmp] = colorcnt;
colornum[colorcnt]++;
}
ll tmp = s.top(); //处理初始点
s.pop();
color[tmp] = colorcnt;
colornum[ colorcnt ]++;
instack[tmp] = false;
}
return ;
}
ll slove(ll now)
{
if(now == G[now][0])
return 1;
if(ans[ G[now][0] ] != 0)
return 1+ans[ G[now][0] ];
else
return 1+slove( G[now][0] );
}
int main()
{
ios::sync_with_stdio(false);
//cin.tie(0),cout.tie(0);
cin >> n;
for(ll i = 1; i <= n; i++)
{
ll y;
cin >> y;
G[i].push_back(y);
}
for(ll i = 1; i <= n; i++)
{
if(dfn[i] == 0)
tarjan(i);
}
/*for(ll i = 1; i <= n; i++)
{
cout << dfn[i] << " " << low[i] << endl;
}
cout << endl;*/
for(ll i = 1; i <= n; i++)
{
if(colornum[ color[i] ] > 1)
ans[i] = colornum[ color[i] ];
}
for(ll i = 1; i <= n; i++)
{
if(ans[i] == 0)
{
ans[i] = slove(i);
}
}
for(ll i = 1; i <= n; i++)
{
cout << ans[i] << endl;
}
return 0;
}
/*
6 8
1 2
2 3
3 6
1 4
4 5
5 1
5 6
2 5
*/
求割点:
对于根节点的判断标准是有两颗不连通的子树就是割点
对于普通节点的判断标准是 low[to] >= dfn[now] (也不能是根节点)
习题:https://www.luogu.org/problemnew/show/P3388
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 2e4+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;
ll n,m,dfn[maxn],low[maxn],color[maxn],colornum[maxn],timing,colorcnt;
vector<ll>G[maxn];
set<ll>ans;
void tarjan(ll now,ll rt) //rt是传进来的根节点,,固定不变,传下去
{
ll child = 0; //判断根节点是否有左右子树
timing++;
dfn[now] = low[now] = timing;
for(ll i = 0; i < G[now].size(); i++)
{
ll to = G[now][i];
if(dfn[to] == 0)
{
child++;
tarjan(to,rt);
low[now] = min(low[now],low[to]);
if(low[to] >= dfn[now] && now != rt) //对于普通节点的判断
{
ans.insert(now);
}
}
low[now] = min(low[now],dfn[to]); //更新自身
}
if(now == rt && child >= 2) //对于根节点判断
{
ans.insert(now);
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
//cin.tie(0),cout.tie(0);
cin >> n >> m;
for(ll i = 1; i <= m; i++)
{
ll x,y;
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
for(ll i = 1; i <= n; i++)
{
if(dfn[i] == 0)
tarjan(i,i);
}
//cout << colorcnt << endl;
cout << ans.size() << endl;
set<ll>::iterator it = ans.begin();
while(it != ans.end())
{
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
求割边:搜索时带着前驱节点,在自己不是自己父亲时更新
low[to] > dfn[now] 是没有等于号的,视频里有讲解为什么不能有等于号
习题:https://ac.nowcoder.com/acm/contest/392/I
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 1e5+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;
ll n,m,ans,timing;
vector<ll>G[maxn];
ll dfn[maxn],low[maxn];
void tarjan(ll now,ll pre)
{
timing++;
low[now] = dfn[now] = timing;
for(ll i = 0; i < G[now].size(); i++)
{
ll to = G[now][i];
if(dfn[to] == 0)
{
tarjan(to,now);
low[now] = min(low[now],low[to]);
if(low[to] > dfn[now])
{
ans++; //to到now 是一条割边
}
}
else if(to != pre) //自己不是自己父亲时更新
{
low[now] = min(low[now],dfn[to]);
}
}
}
int main()
{
ios::sync_with_stdio(false);
//cin.tie(0),cout.tie(0);
cin >> n >> m;
for(ll i = 1; i <= m; i++)
{
ll x,y;
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
for(ll i = 1; i <= n; i++)
{
if(dfn[i] == 0)
{
tarjan(i,i);
}
}
cout << m - ans << endl;
return 0;
}
缩点:缩点与求强连通块类似
习题:http://poj.org/problem?id=2186
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 1e4+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;
ll n,m,timing,ans;
vector<ll>G[maxn];
ll dfn[maxn],low[maxn];
ll color[maxn];
ll colornum[maxn];
ll colorcnt;
ll du[maxn]; //表示第i个强连通分量有几个出边
stack<ll>s;
bool instack[maxn];
void tarjan(ll now)
{
timing++;
s.push(now);
instack[now] = true;
low[now] = dfn[now] = timing;
for(ll i = 0; i < G[now].size(); i++)
{
ll to = G[now][i];
if(dfn[to] == 0)
{
tarjan(to);
low[now] = min(low[now],low[to]);
}
else if(instack[to] == true)
{
low[now] = min(low[now],dfn[to]);
}
}
if(dfn[now] == low[now])
{
ll tmp;
colorcnt++;
while(s.top() != now)
{
tmp = s.top();
s.pop();
color[tmp] = colorcnt;
colornum[colorcnt]++;
instack[tmp] = false;
}
tmp = s.top();
s.pop();
color[tmp] = colorcnt;
colornum[colorcnt]++;
instack[tmp] = false;
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
//cin.tie(0),cout.tie(0);
cin >> n >> m;
for(ll i = 1; i <= m; i++)
{
ll x,y;
cin >> x >> y;
G[x].push_back(y);
}
for(ll i = 1; i <= n; i++)
{
if(dfn[i] == 0)
{
tarjan(i);
}
}
//cout << colorcnt << endl;
for(ll i = 1; i <= n; i++) //进行缩点
{
for(ll j = 0; j < G[i].size(); j++)
{
ll to = G[i][j];
if(color[i] != color[to])
{
du[ color[i] ]++;
}
}
}
ll num = 0,ans = 0; //num出度为0的点
for(ll i = 1; i <= colorcnt; i++)
{
if(du[i] == 0)
{
num++;
ans = colornum[i];
}
}
if(num >= 2 || num == 0)
{
cout << 0 << endl;
}
else if(num == 1)
{
cout << ans << endl;
}
return 0;
}