orzz

定义

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

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

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

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

性质

点双联通分量:

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

边双联通分量:

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

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

点双联通分量

例题

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

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

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

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

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

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

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

重要:对于度为 \(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;
}

边双联通分量

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

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

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

  • \(x \ \% \ 2=1\)\(y=x+1\)
  • \(x \ \% \ 2=0\)\(y=x-1\)

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

例题1

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

例题2

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

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

思路:

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

新图中通向度为 \(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\)

#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\) 点双连通的极大子图。 ↩︎

分类:

技术点:

相关文章:

  • 2021-08-29
  • 2022-12-23
  • 2021-11-28
  • 2021-11-28
  • 2021-08-03
  • 2022-12-23
  • 2022-12-23
  • 2021-10-11
猜你喜欢
  • 2021-12-25
  • 2021-11-24
  • 2022-12-23
  • 2021-10-31
  • 2021-11-28
  • 2021-12-20
相关资源
相似解决方案