定义:
割点集合:对于无向连通图中,删除顶点集 V V V 以及与 V V V 中顶点相连的边之后,图不连通, V V V 是割点集合
点连通度:最小割点集合中的顶点数。删除任意 K − 1 K-1 K−1 个顶点,图仍然联通;删除 K K K 个顶点后图不连通,原图点连通度为 K K K
割边集合:对于无向连通图中,删除边集 E E E 之后,图不连通, E E E 是割边集合
边连通度:最小割边集中的边数。删除任意 K − 1 K-1 K−1 条边后,图仍联通;删除 K K K 条边后,图不联通,原图边连通度为 K K K
点双联通度:点连通度大于1,则图是点双连通的(删除任意一点图仍然联通)
割点:一个图有割点,当且仅当图的点连通度为1,割点集合的唯一元素为割点,一个图可能有多个割点
边双连通度:边连通度大于1,则图是点双连通的(删除任意一边图仍然联通)
桥:一个图有桥,当且仅当图的边连通度为1,割边集合的唯一元素为桥,一个图可能有多个桥
边双连通分量一定是点双连通分量,但点双连通分量不一定是边联通分量
Tarjan
d f n ( u ) dfn(u) dfn(u) 为 u u u 在dfs中的时间戳
l o w ( u ) low(u) low(u) 为 u u u 在不经过父节点的情况下,能够访问到的祖先节点中的最小时间戳
如果 ( u , v ) (u,v) (u,v) 为树枝边,则 l o w ( u ) = m i n ( l o w ( u ) , l o w ( v ) ) low(u) = min(low(u), low(v)) low(u)=min(low(u),low(v))
如果 ( u , v ) (u,v) (u,v) 为后向边,则 l o w ( u ) = m i n ( l o w ( u ) , d f n ( v ) ) low(u) = min(low(u), dfn(v)) low(u)=min(low(u),dfn(v))
割点
满足任意一个条件, u u u 是割点
-
u u u 是树根,且 u u u 的子数个数超过一个
因为 u u u 如果有多个子树,子树之间只能通过树根联通
-
u u u 不是树根,且 ( u , v ) (u,v) (u,v) 是树枝边, d f n ( u ) ≤ l o w ( v ) dfn(u) \le low(v) dfn(u)≤low(v)
因为删除 u u u, v v v 及 v v v 的子树均不能到达 u u u 的祖先
桥
( u , v ) (u,v) (u,v) 是桥,当且仅当 ( u , v ) (u,v) (u,v) 是树枝边,且 d f n ( u ) < l o w ( v ) dfn(u) < low(v) dfn(u)<low(v)。这里没有等号,当 ( u , v ) (u,v) (u,v) 在环上时, ( u , v ) (u,v) (u,v) 不是桥
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define mkp(a, b) make_pair(a, b)
const int maxn = 1e5+10;
const int maxm = 1e6+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;
struct edge{
int to, nxt;
}e[maxm];
int head[maxn], tot;
void add(int a, int b){
e[++tot].nxt = head[a];
e[tot].to = b;
head[a] = tot;
}
int dfn[maxn], low[maxn], vis[maxn];
int dfntot;
vector<int> cutv;
vector<pii> bridge;
void tarjan(int u, int fa){
dfn[u] = low[u] = ++dfntot;
vis[u] = 1;
int flag = 0, sonsize = 0;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == fa)
continue;
if(!dfn[v]){
sonsize++;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if((u==1&&sonsize>1) || (u!=1&&dfn[u]<=low[v]))
flag = 1;
if(dfn[u] < low[v])
bridge.push_back(mkp(u, v));
}
else
low[u] = min(low[u], dfn[v]);
}
if(flag)
cutv.push_back(u);
}
int n, m;
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i++){
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
tarjan(1, 0);
cout << "cutv:" << endl;
for(int i = 0; i < cutv.size(); i++)
cout << cutv[i] << " ";
cout << endl;
cout << "bridge:" << endl;
for(int i = 0; i < bridge.size(); i++)
cout << "(" << bridge[i].fi << "," << bridge[i].se << ")" << " ";
cout << endl;
return 0;
}
双连通分量
点双连通分量:
建立一个栈,存储当前点双连通分量,每找到一条树枝边或者后向边,把边入栈。如果
d
f
n
(
u
)
≤
l
o
w
(
v
)
dfn(u) \le low(v)
dfn(u)≤low(v) 成立,即
u
u
u 是割点,出栈直到
(
u
,
v
)
(u,v)
(u,v) 也出栈。取出与这些边相连的点,即点双连通分量。
割点可以属于多个点双连通分量,其他点和边只属于一个点双连通分量。对于任意两个点双连通分量,最多只有一个公共点,即割点
边双连通分量
求出桥后,把桥删除,剩下的每个连通块就是一个边双连通分量。
桥不属于任何一个边双连通分量,其余边和点只属于一个边双连通分量
一个结论:
有桥的连通图,如果加边变成边双连通图?
先求出桥,然后删除桥,剩下的都是边双联通块,把每块缩点,在加上桥边,形成一棵树。
统计树中度为1的点,即叶结点个数
c
n
t
cnt
cnt,在树上至少添加
⌊
c
n
t
+
1
2
⌋
\lfloor \frac{cnt+1}{2}\rfloor
⌊2cnt+1⌋
P2860 [USACO06JAN]Redundant Paths G
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 5E3+10;
const int M = 1E4+10;
int head[N], tot = 1;
struct edge{
int to, nxt;
}e[M<<1];
void add(int a, int b){
e[++tot].nxt = head[a];
e[tot].to = b;
head[a] = tot;
}
int n, m, ans;
int dfn[N], low[N], bridge[M<<1], dfntot;
int c[N], dcc, deg[N];
void tarjan(int u, int edge){
dfn[u] = low[u] = ++dfntot;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(!dfn[v]){
tarjan(v, i);
low[u] = min(low[u], low[v]);
if(dfn[u] < low[v])
bridge[i] = bridge[i^1] = 1;
}
else if(i != (edge^1))
low[u] = min(low[u], dfn[v]);
}
}
void dfs(int u){
c[u] = dcc;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(c[v] || bridge[i])
continue;
dfs(v);
}
}
int main(){
cin >> n >> m;
for(int i = 1; i <= m; i++){
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
for(int i = 2; i <= tot; i++)
if(!dfn[e[i].to])
tarjan(e[i].to, i);
for(int i = 1; i <= n; i++){
if(!c[i]){
dcc++;
dfs(i);
}
}
for(int j = 1; j <= n; j++){
for(int i = head[j]; i; i = e[i].nxt){
if(c[j] != c[e[i].to])
deg[c[e[i].to]]++;
}
}
for(int i = 1; i <= dcc; i++){
if(deg[i] == 1)
ans++;
}
ans = (ans+1) / 2;
cout << ans << endl;
}
Knights of the Round Table
题意:
n n n 个骑士圆桌会议,有些骑士互相仇视,必须满足:
- 互相仇视的骑士在圆桌上不能相邻,每个骑士必须有相邻的其他骑士
- 圆桌上骑士数为奇数
某些骑士不可能坐下,询问不可能坐下的骑士数
解析:
把骑士看成顶点,不仇视的骑士之间连边建图。把所有能坐一起的骑士分为一组,有的骑士可能出现在多组中,每一组是点双连通分量。
题目要求顶点数为奇数的环。
- 如果一个点双联通分量中存在一个奇环,则该点双内所有顶点都在某个奇环内
- 一个点双连通分量含有奇环,当且仅当不是二分图
因此,先求出补图,然后在补图中求出每个点双连通分量,对每个点双连通分量判断是否是二分图。如果是二分图,则标记该点双连通分量所有结点,即可以出现在圆桌上。最后统计未标记点的个数
https://blog.csdn.net/sinat_40872274/article/details/99660385