双联通分量

本文详细介绍了图论中的割点、割边及其概念,包括点双联通分量和边双联通分量的定义、性质以及在实际问题中的应用。通过实例演示了如何使用弹点法和AK cxr法求解点双联通分量和边双联通分量,以及如何利用这些概念解决图的连通性和最少边添加问题。
摘要由CSDN通过智能技术生成

更好的阅读体验

定义

割点:给定一无向连通图,对于其中一点 u u u,若从图中删掉 u u u 和所有与 u u u 相连的边后,原图分裂成成 2 2 2 个或以上不相连的子图,则称 u u u 为原图的割点(或割顶)。

割边:给定一无向连通图,对于其中一边 ( u , v ) (u,v) (u,v),若从图中删掉 ( u , v ) (u,v) (u,v) 后,原图分裂成 2 2 2 个或以上不相连的子图,则称 ( u , v ) (u,v) (u,v) 为原图的割边(或桥)。

点双联通分量:一张无向连通图,若不存在割点,且任意两点间均有两条或以上的点不重复路径,则称为:点双联通分量。

边双联通分量:一张无向连通图,若不存在割边,且任意两点间均有两条或以上的边不重复路径,则称为:边双联通分量。

性质

点双联通分量:

  • 对于一个割点,他应该存在于 2 2 2 个及多个点双联通分量内。
  • 对于不是割点的点,他只能存在于一个点双联通分量中。
  • 除了两点一线的情况,其余的点双连通分量一定是边双连通分量,反之不一定。
  • G G G 中的边无论是否是桥,都最多属于一个点双连通分量;
  • 对于一个点双联通分量中的任意两个点,它们之间都有至少两条点不重复的路径。

边双联通分量:

  • 割边不属于任意边双联通分量,而其它非割边的边都属于且仅属于一个边双联通分量。
  • 对于一个边双联通分量中的任意两个点,它们之间都有至少两条边不重复的路径。
  • 当一个双连通分量中的边数大于点数时,其中所有的边都属于两个及以上的环。
  • 对于一连通的无向图,其桥的数量一定等于边双连通分量的数量 − 1 -1 1

重 要 性 质 : N 个 点 M 条 边 的 图 , 问 如 果 加 一 条 边 , 最 少 可 以 剩 下 多 少 个 桥 ? 边 双 联 通 缩 点 以 后 形 成 一 棵 树 , 所 有 树 边 均 为 桥 。 环 上 的 边 显 然 不 是 桥 , 所 以 我 们 使 得 树 上 最 长 的 一 条 链 变 为 环 , 也 就 是 直 径 。 那 么 答 案 就 是 : 原 来 的 桥 数 − 直 径 。 坑 点 : 有 重 边 ! ! ! 重 边 自 然 不 算 是 桥 了 。 \color{red}{重要性质:N 个点 M 条边的图,问如果加一条边, 最少可以剩下多少个桥? \\ 边双联通缩点以后形成一棵树,所有树边均为桥。 \\ 环上的边显然不是桥,所以我们使得树上最长的一条链变为环,也就是直径。 \\ 那么答案就是:原来的桥数 - 直径。 \\ 坑点:有重边!!!重边自然不算是桥了。 } NM使

点双联通分量

例题

对于求点双连通分量的方法,我比较推荐 “弹点法”:

假设我们遍历到了无向边 ( u , v ) (u,v) (u,v) 且点 u u u 是割点,再想到关于割点的定理:

对于一个割点,他应该存在于 2 2 2 个及多个点双联通分量内。

如果 u u u 是割点,那么我们把 u u u 删掉,原图就被分成了 v v v v v v 的子树 和 剩下的节点 至少 2 2 2 个子图。

如果遇到 dfn[u] <= low[v],那么此时, v v v v v v 的子树 和 u u u 就是一个点双联通分量。

然后我们将 v v v v v v 的子树弹出并记录。

注意:此时,不能将 u u u 弹出,根据 点双联通分量的定义1 u u u 可能存在于多个点双联通分量之中。

重要:对于度为 0 0 0 的点,需要特判,因为:孤点也是一个点双联通分量。

#include <bits/stdc++.h>
using namespace std;

struct Fastio
{
	template <typename T>
	inline Fastio operator>>(T &x)
	{
		x = 0;
		char c = getchar();
		while (c < '0' || c > '9')
			c = getchar();
		while (c >= '0' && c <= '9')
			x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
		return *this;
	}
	inline Fastio &operator<<(const char *str)
	{
		int cur = 0;
		while (str[cur])putchar(str[cur++]);
		return *this;
	}
	template <typename T>
	inline Fastio &operator<<(T x)
	{
		if (x == 0)
		{
			putchar('0');
			return *this;
		}
		if (x < 0) putchar('-'), x = -x;
		static int sta[45];
		int top = 0;
		while (x) sta[++top] = x % 10, x /= 10;
		while (top) putchar(sta[top] + '0'), --top;
		return *this;
	}

} io;

int n, m, rt, ans, cnt_node, cntn;

int cnt;
array<int, 2000005> head;
struct abc
{
	int to, nxt;
};
array<abc, 2000005> dd;

array<int, 2000005> dfn, low;
stack<int> s;

vector<int> cutt[2000005];

inline void add(int u, int v)
{
	dd[++cnt].to = v;
	dd[cnt].nxt = head[u];
	head[u] = cnt;
}

inline void tarjan(int u)
{
	dfn[u] = low[u] = ++cnt_node;
	s.push(u);
//	int flag = 0;
	if(rt == u && !head[u])
	{
		cntn++;
		cutt[cntn].push_back(u);
		return;
	}
	for (int e = head[u]; e; e = dd[e].nxt)
	{
		int v = dd[e].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[v], low[u]);
			if(dfn[u] <= low[v])
			{
//				cout << v << endl;
//				flag++;
//				if(u != rt || flag > 1)
//				{
					cntn++;
					while(1)
					{
						int now = s.top();
						s.pop();
						cutt[cntn].push_back(now);
						if(v == now) break;
					}
					cutt[cntn].push_back(u);
//				}
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

signed main()
{
	io >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		int u, v;
		io >> u >> v;
		add(u, v);
		add(v, u);
	}
	for(int i = 1; i <= n; ++i)
		if(!dfn[i]) tarjan(rt = i);
	for(int i = 1; i <= cntn; ++i)
	{	
		for(int j = 0; j < cutt[i].size(); ++j)
		{
			cout << cutt[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

边双联通分量

对于求边双连通分量的方法,我比较推荐 A K   c x r AK \ cxr AK cxr 法:

v i s vis vis 数组记录边有没有被遍历过,每遇到一条没遍历到的边,就将他这条边和他的反边都标记为 1 1 1

若已知一条边的编号为 x x x,则他的反边的编号 y y y 的计算方法:

  • x   %   2 = 1 x \ \% \ 2=1 x % 2=1 y = x + 1 y=x+1 y=x+1
  • x   %   2 = 0 x \ \% \ 2=0 x % 2=0 y = x − 1 y=x-1 y=x1

当点 u u u t a r j a n tarjan tarjan 结束后,如果 l o w [ u ] = d f n [ u ] low[u]=dfn[u] low[u]=dfn[u],说明 u u u 的子树中没有后向边。此时不断地弹出栈顶的点,标记其所属的边双连通分量,直到 u u u 出栈。

例题1

给定一个无向图,试求最少要加入几条边,才能使得该图变成一个双连通图。

例题2

给定一个无向图,至少还要修建多少条道路, 才能使得任意一条道路被占领的情况下, 其任意两个城市都可以互相到达?且如果一条道路被占领,所有重边都会被占领。

注 意 : 对 于 这 例 题 2 , 不 判 重 边 的 都 是   . . ? ? ? \color{red}{注意:对于这例题 2,不判重边的都是 \ ..???} 2 ..???

思路:

Tarjan 缩点, a n s = ( ans =( ans=新图中度为 1 1 1 的节点数 + 1 ) / 2 + 1 )/ 2 +1/2

新图中通向度为 1 1 1 的节点的边即为桥,切断则图不连通。

所以要使每个点的度都大于 1 1 1

连接两个度为 1 1 1 的节点可以同时解决它们。

如果有剩余的点就特供给它一条边。

例题 1 1 1

#include<bits/stdc++.h>
using namespace std;

struct Fastio
{
    template <typename T>
    inline Fastio operator>>(T &x)
    {
        x = 0;
        char c = getchar();
        while (c < '0' || c > '9')
            c = getchar();
        while (c >= '0' && c <= '9')
            x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
        return *this;
    }
    inline Fastio &operator<<(const char *str)
    {
        int cur = 0;
        while (str[cur])
            putchar(str[cur++]);
        return *this;
    }
    template <typename T>
    inline Fastio &operator<<(T x)
    {
        if (x == 0)
        {
            putchar('0');
            return *this;
        }
        if (x < 0)
            putchar('-'), x = -x;
        static int sta[45];
        int top = 0;
        while (x)
            sta[++top] = x % 10, x /= 10;
        while (top)
            putchar(sta[top] + '0'), --top;
        return *this;
    }

} io;

#define _ 20005

int n, m, ans;

int tot, head[_], to[_ << 1], nxt[_ << 1];

int dol[_];

int cnt_node, cntn, low[_], dfn[_], id[_], vis[_ << 1];
stack<int> s;

int u[_], v[_];

int js(int x)
{
	return (x % 2) ? x + 1 : x - 1;
}

void add(int u, int v)
{
	to[++tot] = v;
	nxt[tot] = head[u];
	head[u] = tot;
}

void tarjan(int u)
{
	low[u] = dfn[u] = ++cnt_node;
	s.push(u);
	for(int i = head[u]; i; i = nxt[i])
		if(!vis[i])
		{
			vis[i] = vis[js(i)] = 1;
			if(!dfn[to[i]])
			{
				tarjan(to[i]);
				low[u] = min(low[u], low[to[i]]);
			}
			else low[u] = min(low[u], dfn[to[i]]);
		}
	if(dfn[u] == low[u])
	{
		cntn++;
		while(1)
		{
			int now = s.top();
			s.pop();
			id[now] = cntn;
			if(now == u) break;
		}
	}
}

signed main()
{
//	freopen("P2860_2.in", "r", stdin);
//	freopen("2860_2.ans", "w", stdout);
	io >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		io >> u[i] >> v[i];
		add(u[i], v[i]);
		add(v[i], u[i]);
	}
	for(int i = 1; i <= n; ++i)
		if(!dfn[i]) tarjan(i);
	for(int i = 1; i <= m; ++i)
	{
		if(id[u[i]] != id[v[i]])
		{
			dol[id[u[i]]]++;
			dol[id[v[i]]]++;
		}
	}
	for(int i = 1; i <= cntn; ++i)
		if(dol[i] == 1) ans++;
	io << (ans + 1) / 2 << "\n";
}

例题 2 2 2

#include<bits/stdc++.h>
using namespace std;

struct Fastio
{
	template <typename T>
	inline Fastio operator>>(T &x)
	{
		x = 0;
		char c = getchar();
		while (c < '0' || c > '9')
			c = getchar();
		while (c >= '0' && c <= '9')
			x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
		return *this;
	}
	inline Fastio &operator<<(const char *str)
	{
		int cur = 0;
		while (str[cur])
			putchar(str[cur++]);
		return *this;
	}
	template <typename T>
	inline Fastio &operator<<(T x)
	{
		if (x == 0)
		{
			putchar('0');
			return *this;
		}
		if (x < 0)
			putchar('-'), x = -x;
		static int sta[45];
		int top = 0;
		while (x)
			sta[++top] = x % 10, x /= 10;
		while (top)
			putchar(sta[top] + '0'), --top;
		return *this;
	}

} io;

#define _ 20005

int n, m, ans;

int tot, head[_], to[_ << 1], nxt[_ << 1];

int dol[_];

int cnt_node, cntn, low[_], dfn[_], id[_], vis[_ << 1];
stack<int> s;

int u[_], v[_];

bool opt[_ << 1];

bool flagg[2001][2001];

int js(int x)
{
	return (x % 2) ? x + 1 : x - 1;
}

void add(int u, int v)
{
	to[++tot] = v;
	nxt[tot] = head[u];
	head[u] = tot;
}

void tarjan(int u)
{
	low[u] = dfn[u] = ++cnt_node;
	s.push(u);
	for(int i = head[u]; i; i = nxt[i])
		if(!vis[i])
		{
			vis[i] = vis[js(i)] = 1;
			if(!dfn[to[i]])
			{
				tarjan(to[i]);
				low[u] = min(low[u], low[to[i]]);
			}
			else low[u] = min(low[u], dfn[to[i]]);
		}
	if(dfn[u] == low[u])
	{
		cntn++;
		while(1)
		{
			int now = s.top();
			s.pop();
			id[now] = cntn;
			if(now == u) break;
		}
	}
}

signed main()
{
//	freopen("缩点D题data4.in","r",stdin);
	io >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		io >> u[i] >> v[i];
		if(!flagg[u[i]][v[i]] && !flagg[v[i]][u[i]])
		{
			add(u[i], v[i]);
			add(v[i], u[i]);
			flagg[u[i]][v[i]] = flagg[v[i]][u[i]] = 1;
			opt[i] = 1;
		}
	}
//	cout<<endl;
	for(int i = 1; i <= n; ++i)
		if(!dfn[i]) tarjan(i);

	for(int i = 1; i <= m; ++i)
	{
		if(id[u[i]] != id[v[i]] && opt[i]) dol[id[u[i]]]++, dol[id[v[i]]]++;
	}

	for(int i = 1; i <= cntn; ++i)
		if(dol[i] == 1) ans++;
	io << (ans + 1) / 2 << "\n";
}

  1. G G G 点双连通的极大子图。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值