Tarjan点的双联通(寻找割点)

问题概述:给你一个无向联通图,你要在图中标记一些点,使得这个图中的任意一个点消失了,剩余的点都可以通

一条路径到达你某个标记的点。问你最少需要选择多少个点,并且在最优的情况下有多少总选点方案,(每个样

例输入的第一个数m表示图中有多少条边,当m为0时读入结束)

输入样例:                                对应输出:

9                                                Case 1: 2 4

1 3   4 1

3 5   1 2

2 6   1 5

6 3   1 6   3 2

UVALive - 5135


点双连通图:该无向图中的任意两个顶点间都存在两条点不相交的路径

(或是说该图中去掉任意一个点都不会影响联通性)

定理:

①如果一个连通图不是点双联通图,那么必定存在至少一个点,如果这个点消失,图就会被分成多块,这样的点也

被称为割点

②点双联通分量一定是边双联通分量(除两点一线的特殊情况),反之不一定

③点双联通分量可以有公共点


Trajan主要原理:

time[k]:第一次遍历到k点的时间

low[k]:k点所在点双联通分量子图中第一个被搜到的点的time值

vis[k]:k点是否已经遍历

主要思路:一般来讲无论求解点双联通还是边双连通都是边入栈,如果点入栈,而因为性质③,你将一个点出

后,还可能有别的点双联通分量包含它,所以点入栈会导致一些错误和麻烦,但这里有种方法可以不需要栈,大概

思路是把所有的割点找出来(割点time[]值一定会小于等于他搜索子树儿子点的low[]值,至于为什么?很显然如果一个点不是割点,那么它必定在一环上或它自身是一个点双联通分量,考虑前者:那么这个点的深搜子树一定

与他的某个父亲相连接,这样这个儿子的low[]值就会是这个点某个父亲的low[]值,所以一定小于本身的time[]

值;考虑后者,它就不存在深搜子树更有儿子,根本不会比较),然后再搜索一遍找出所有在同一点双联通分量

中的点即可。

注意:搜索对于根节点因为没有父亲节点,所以无法通过上述方法判断根是否是割点,那怎么办?如果根有两个

搜索子树,那么它一定是个点,反之不是

搜索过程:

→当点k有与点c相连,如果此时(time[k]时)c没有搜过,搜索c点,当c点及其子树搜索完毕回溯后,low[k] =

 min(low[k], low[c]),如果此时low[c]的值>=time[k]的值,则说明点k是割点,否则不是

→当点k有与点c相连,如果此时(time[k]时)c已经搜过,low[k] = min(low[k], time[c])

期间保证:每个点每条边都只被搜索1次,且必须搜索1次,复杂度n+m,


此题题解:

看懂这题后第一眼想到是此图有多少个点双联通分量,那么就至少需要标记多少个点,然后情况数就是所有点双联

通分量中点个数的乘积,但没这么简单,要注意以下两点:

①:如果这个图本身是个点双联通分量,那么还是必须要标记两个点,因为有可能标记的点会被去掉,情况数就是

n*(n-1)/2,其中n为图中点的数量

②:如果一个点双联通分量中有不止一个割点,那么这个点双联通分量中就不需要标记任何一个点,因为其中一个

割点被去掉,还可以走另外一个割点到另一个双连通分量中


#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
vector<int> G[50005];
set<int> st;
int n, t, sum, cutsum, vis[50005], low[50005], time[50005], cut[50005];
void Tarjan(int u, int p);
void Sech(int u);
int main(void)
{
	int i, m, u, v, cas = 1;
	long long bet, ans;
	while(scanf("%d", &m), m!=0)
	{
		cutsum = 0;
		t = n = 1;
		memset(vis, 0, sizeof(vis));
		memset(cut, 0, sizeof(cut));
		for(i=1;i<=m+2;i++)
			G[i].clear();
		for(i=1;i<=m;i++)
		{
			scanf("%d%d", &u, &v);
			G[u].push_back(v);
			G[v].push_back(u);
			n = max(n, max(v, u));
		}
		Tarjan(1, 0);
		memset(vis, 0, sizeof(vis));
		st.clear();
		ans = 1, bet = 0;
		for(i=1;i<=n;i++)
		{
			sum = 0;
			if(vis[i]==0 && cut[i]==0)
			{
				st.clear();
				Sech(i);
				if(st.size()==1)
				{
					ans *= sum;
					bet++;
				}
			}
		}
		if(cutsum==0)
			printf("Case %d: 2 %lld\n", cas++, (long long)n*(n-1)/2);
		else
			printf("Case %d: %lld %lld\n", cas++, bet, ans);
	}
	return 0;
}

void Tarjan(int u, int p)
{
	int i, v, child = 0;
	vis[u] = 1;
	low[u] = time[u] = t++;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		if(vis[v]==0)
		{
			child++;
			Tarjan(v, u);
			low[u] = min(low[v], low[u]);
			if(p==0 && child>1 || p!=0 && low[v]>=time[u])
				cut[u] = 1,  cutsum++;
		}
		else
			low[u] = min(low[u], time[v]);
	}
}

void Sech(int u)
{
	int i, v;
	sum++;
	vis[u] = 1;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(cut[v])
			st.insert(v);			/*因为一个割点可能会被不同的点搜到,所以这里要用set防重*/
		if(cut[v] || vis[v])
			continue;
		Sech(v);
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值