算法笔记-割边和桥

定义:

割点集合:对于无向连通图中,删除顶点集 V V V 以及与 V V V 中顶点相连的边之后,图不连通, V V V 是割点集合

点连通度:最小割点集合中的顶点数。删除任意 K − 1 K-1 K1 个顶点,图仍然联通;删除 K K K 个顶点后图不连通,原图点连通度为 K K K

割边集合:对于无向连通图中,删除边集 E E E 之后,图不连通, E E E 是割边集合

边连通度:最小割边集中的边数。删除任意 K − 1 K-1 K1 条边后,图仍联通;删除 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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值