双联通分量

更好的阅读体验

定义

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

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

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

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

性质

点双联通分量:

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

边双联通分量:

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

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

点双联通分量

例题

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

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

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

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

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

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

注意:此时,不能将 uu 弹出,根据 点双联通分量的定义(图 GG 点双连通的极大子图),uu 可能存在于多个点双联通分量之中。

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

#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;
}

边双联通分量

对于求边双连通分量的方法,我比较推荐 AK cxrAK \ cxr 法:

visvis 数组记录边有没有被遍历过,每遇到一条没遍历到的边,就将他这条边和他的反边都标记为 11

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

  • x % 2=1x \ \% \ 2=1y=x+1y=x+1
  • x % 2=0x \ \% \ 2=0y=x1y=x-1

当点 uutarjantarjan 结束后,如果 low[u]=dfn[u]low[u]=dfn[u],说明 uu 的子树中没有后向边。此时不断地弹出栈顶的点,标记其所属的边双连通分量,直到 uu 出栈。

例题1

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

例题2

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

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

思路:

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

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

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

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

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

例题 11

#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";
}

例题 22

#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";
}
posted @ 2021-08-08 15:33  蒟蒻orz  阅读(7)  评论(0编辑  收藏  举报  来源