割点
- 在无向图中,所有能互通的点构成了一个连通分量,在这些连通分量中有一些关键点,如果删除它,会把这个连通分量分成两个或者更多,这种点被称为割点
- 如下图的节点
3
,
4
,
5
3,4,5
3,4,5均为割点
- 定理1:T的根节点s是割点,当且仅当s有两个或更多的子节点
- 定理2:T的非根节点u是割点,当且仅当u存在一个子节点v,v及其后代都没有回退边连回u的祖先
- 利用这样两个定理,我们可以在
O
(
n
)
O(n)
O(n)的时间复杂度内求出一个图的所有割点
- 和SCC的求法相似的。设
n
u
m
[
i
]
num[i]
num[i]表示
D
f
s
Dfs
Dfs对每个点的访问顺序,
l
o
w
[
i
]
low[i]
low[i]表示
i
i
i能够连到祖先的
n
u
m
num
num
- 如果最后发现
l
o
w
[
v
]
≥
n
u
m
[
u
]
low[v]\geq num[u]
low[v]≥num[u],那就说明父亲节点
u
u
u进入递归的顺序总是小于等于子节点
v
v
v回退的位置,那就说明
u
u
u是一个割点
模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<int> > g(n + 1);
for(int i=0;i<m;i++){
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> num(n + 1), low(n + 1);
vector<bool> iscut(n + 1);
int dfn = 0;
function<void(int, int)> Dfs = [&](int u, int fa){
low[u] = num[u] = ++dfn;
int child = 0;
for(auto v : g[u]){
if(!num[v]){
child += 1;
Dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= num[u] && u != fa){
iscut[u] = true;
}
}else if(num[v] < num[u] && v != fa){
low[u] = min(low[u], num[v]);
}
}
if(u == fa && child >= 2){
iscut[u] = true;
}
};
for(int i=1;i<=n;i++){
if(!num[i]){
Dfs(i, i);
}
}
int tot = 0;
for(int i=1;i<=n;i++){
if(iscut[i]) tot += 1;
}
cout << tot << '\n';
for(int i=1;i<=n;i++){
if(iscut[i]){
cout << i << ' ';
}
}
return 0;
}
割边
- 同理,在一个连通分量中,如果删除一条边,把这个连通分量分成了两个,那么这条边就是割边
- 类似的,如果子节点的回退位置在父亲节点进入递归之后(不相等),那么说明它和父亲节点之间的连边就是割边
模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<int> > g(n + 1);
for(int i=0;i<m;i++){
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> low(n + 1), num(n + 1);
int dfn = 0;
int ans = 0;
function<void(int, int)> Dfs = [&](int u, int fa){
low[u] = num[u] = ++dfn;
for(auto v : g[u]){
if(!num[v]){
Dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > num[u]){
ans += 1;
}
}else if(v ^ fa){
low[u] = min(low[u], num[v]);
}
}
};
for(int i=1;i<=n;i++){
if(!num[i]){
Dfs(i, i);
}
}
cout << ans;
return 0;
}
确认模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
int n;
int tt;
scanf("%d", &tt);
for(int kase=1;kase<=tt;kase++){
scanf("%d", &n);
vector<vector<int> > g(n + 1);
for(int i=0;i<n;i++){
int u, v, m;
scanf("%d (%d)", &u, &m);
for(int j=0;j<m;j++){
scanf("%d", &v);
g[u].push_back(v);
}
}
vector<int> low(n + 1), num(n + 1);
int dfn = 0;
int ans = 0;
vector<pair<int, int> > vs;
function<void(int, int)> Dfs = [&](int u, int fa){
low[u] = num[u] = ++dfn;
for(auto v : g[u]){
if(!num[v]){
Dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > num[u]){
vs.push_back(make_pair(min(u, v), max(u, v)));
ans += 1;
}
}else if(v ^ fa){
low[u] = min(low[u], num[v]);
}
}
};
for(int i=1;i<=n;i++){
if(!num[i]){
Dfs(i, i);
}
}
printf("Case %d:\n", kase);
printf("%d critical links\n", ans);
sort(vs.begin(), vs.end());
for(auto i : vs){
printf("%d - %d\n", i.first, i.second);
}
}
return 0;
}
点双连通分量
- 在一个连通图中选任意两个点,如果他们之间至少存在两条点不重复的路径,称为点双连通,换句话说,无论删除除了这两个点之外的其余哪一个点,这两个点仍然能够找到通路,这两个点就是点双连通的
- 一个图中的极大连通子图称为点双连通分量,在一个点双连通分量中去掉任意一个点,其他点仍然是连通的
- 也就是说,点双连通分量中没有割点
http://poj.org/problem?id=1523 - 求一个图中有多少个点删除之后会产生新的连通分量,并输出产生的连通分量,代码如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#include <iomanip>
using namespace std;
const int N = 2e3 + 100;
struct Edge{
int next;
int to;
int val;
}edge[N];
int head[N], low[N], num[N];
int cnt = 0;
void Add_Edge(int u, int v, int w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
int subnets[N];
bool f = false;
int dfn = 0;
int n;
void tarjan(int u, int fa){
low[u] = num[u] = ++dfn;
int child = 0;
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(!num[v]){
child += 1;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if((u == fa && child > 1) || (low[v] >= num[u] && u != fa)){
f = true;
subnets[u] += 1;
}
}else if(v != fa){
low[u] = min(low[u], num[v]);
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int u, v;
int kase = 1;
while(true){
cnt = dfn = 0;
memset(head, -1, sizeof head);
memset(num, 0, sizeof num);
memset(low, 0, sizeof low);
memset(subnets, 0, sizeof subnets);
f = false;
n = -1;
while(cin >> u && u){
cin >> v;
Add_Edge(u, v, 1);
Add_Edge(v, u, 1);
n = max(max(u, v), n);
}
if(n == -1) break;
for(int i=1;i<=n;i++){
if(!num[i]){
tarjan(i, i);
}
}
cout << "Network #" << kase << '\n';
if(!f) cout << " No SPF nodes\n";
else{
for(int i=1;i<=n;i++){
if(subnets[i] > 0){
cout << " SPF node " << i << " leaves " << subnets[i] + 1 << " subnets\n";
}
}
}
kase += 1;
cout << '\n';
}
return 0;
}
边双连通分量
- 如果任意两点之间至少存在两条边不重复的路径,称为“边双连通”,也就是桥不属于任何边双连通分量,那么我们可以把所有桥删除掉,划分出的若干个连通分量就是若干个边双连通分量
- 这里使用前向星存图,标记所有割边,然后再次
D
f
s
Dfs
Dfs的时候不走这些割边,给不同的连通分量编号即可进行相关运算
模板题 - 问所有边双连通分量各自的异或和
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 100;
struct Edge{
int next;
int to;
int val;
}edge[N];
int head[N], ans[N];
int cnt = 0;
void Add_Edge(int u, int v, int w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> w(n + 1);
memset(head, -1, sizeof head);
for(int i=1;i<=n;i++){
cin >> w[i];
}
for(int i=0;i<m;i++){
int u, v;
cin >> u >> v;
Add_Edge(u, v, 1);
Add_Edge(v, u, 1);
}
vector<int> low(n + 1), num(n + 1);
vector<bool> bridge(cnt + 1);
int dfn = 0;
function<void(int, int)> tarjan = [&](int u, int fa){
low[u] = num[u] = ++dfn;
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
if(!num[v]){
tarjan(v, u);
low[u] = min(low[v], low[u]);
if(low[v] > num[u]){
bridge[i] = true;
bridge[i ^ 1] = true;
}
}else if(v ^ fa){
low[u] = min(low[u], num[v]);
}
}
};
for(int i=1;i<=n;i++){
if(!num[i]){
tarjan(i, i);
}
}
vector<int> bccNo(n + 1);
int bccCount = 0;
function<void(int)> Dfs = [&](int u){
bccNo[u] = bccCount;
ans[bccCount] ^= w[u];
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(bridge[i]) continue;
if(bccNo[v] == 0){
Dfs(v);
}
}
};
for(int i=1;i<=n;i++){
if(bccNo[i] == 0){
bccCount += 1;
Dfs(i);
}
}
sort(ans + 1, ans + bccCount + 1);
for(int i=1;i<=bccCount;i++) cout << ans[i] << '\n';
return 0;
}
总结
- 割点和割边应该来说是比较常见的问题,上次济南热身赛好像就有个割边的题,这些都是针对于无向图的,记不太清楚就把模板打好带着