p1691 [hdj3394_点双联通+桥]railway

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/84673297

题目

描述 Description
有一个公园有n个景点,这n个景点由m条无向道路连接而成。公园的管理员准备规划一一些形成回路的参观路线。如果一条道路被多条参观路线公用,那么这条路是冲突的;如果一条道路没在任何一个回路内,那么这条路是多余的道路。
问分别有多少条有冲突的路和多余的路
输入格式 Input Format
包括多组数据
每组数据第一行2个整数n,m
接下来m行,每行2个整数x,y,表示从x到y有一条无向边。
输入数据以n=0,m=0结尾
输出格式 Output Format
一行2个整数,表示你要求的多余的道路和冲突的道路的数量。
样例输入 Sample Input

8 10
0 1
1 2
2 3
3 0
3 4
4 5
5 6
6 7
7 4
5 7
0 0

样例输出 Sample Output

1 5
时间限制 Time Limitation
1s
注释 Hint
【数据范围】
n<=10000
m<=100000
0<=x,y<n
每个测试点有10组数据。
来源 Source
hdoj 3394
题面+数据来自2018级 宋逸群

题解

查了半天,是道桥与点双的板子题(然而我打了边双。。。。。。。。)。。。。。。。
一.解释一下点双:点双连通分量:对于一个连通图,如果任意两点至少存在两条“点不重复”的路径,则说这个图是点双连通的,简单来说就是任意两条边都在同一个简单环中,即内部无割顶。

二.先尝试解释一下要求输出的两个东西究竟是什么:
1.多余边:不在任何环中,一定是桥。
2.冲突边:如果一个环内的边数大于点数,那么这个环内所有边都是“冲突边”。其实就是点双了。

三.怎么判断一个双连通分量中环的个数呢?根据点数跟边数的关系 。
1.当点数=边数,形成一个环 。
2.当点数>边数(一条线段,说明这条边是桥) 。
3.当点数<边数,那么就含1个以上的环了。

代码

简陋注释,可能有错,欢迎大家指出!!!!!!

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int maxm=10010;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int bridge,vDCC;

int ver[maxn*2],Next[maxn*2],head[maxm],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}

int belong[maxm],cnt=0;
bool instack[maxm];
void GetvDCC()
{
	int num=0;
	for (int j=0;j<cnt;++j)
	{
		int x=belong[j];//属于第0个点双的点 
		for (int i=head[x];i!=-1;i=Next[i])//邻接表的dfs 
		{
			int y=ver[i];
			if (instack[y]) num++;//如果说他被访问过,那他肯定不在栈里,也就说是点双了 
		}
	}
	num>>=1;//无向图,所以点会过两次 
	if (num>cnt) vDCC+=num;//如果一个环内的边数大于点数,那么这个环内所有边都是“冲突边”
}
int Stack[maxm],top;
int dfn[maxm],low[maxm],id=0;
void tarjan(int x,int fa)//有自环时不加自环的边 
{ //点双连通缩点方法:清空路径,枚举ver,Next数组中存储的路径,建立双向边
	low[x]=dfn[x]=++id;
	Stack[top++]=x;
	for (int i=head[x];i!=-1;i=Next[i])//此处的判断条件需要注意 
	{
		int y=ver[i];
		if (y==fa) continue;
		//如果这条边的另一个结点正好是这个结点的双亲(相当于就是从这条边过来的,没必要再走一次) 
		if (!dfn[y])//y结点还未访问过 
		{
			tarjan(y,x);//访问y结点 
			low[x]=min(low[x],low[y]);//更新low 
			if (low[y]>dfn[x]) ++bridge;//正如上面所说:当点数>边数(一条线段,说明这条边是桥) 
			if (low[y]>=dfn[x])
			{
				int k;
				cnt=0;
				memset(instack,0,sizeof(instack));//注意在进行标记前要把它清空 
				do
				{
					k=Stack[--top];//取出栈顶 
					belong[cnt++]=k;//k属于第cnt个点双 
					instack[k]=1;//他已不在栈里 
				}while (k!=y);
				belong[cnt++]=x;
				instack[x]=1;
				GetvDCC();
			}
		}
		else//与有向图区分,此处else不需要判别v节点是否在栈内
			low[x]=min(low[x],dfn[y]);
	}
}
int main()
{
	while (1)
	{
		int n=read(),m=read();
		if (!n && !m) break;
		
		memset(head,-1,sizeof(head));
		//注意这里,所以下面tarjan函数里的for循环里的判断条件为i!=-1 
		len=0;
		while (m--)
		{
			int x=read(),y=read();//结点是从零开始的 
			add(x,y),add(y,x);//无向图 
		}
		
		memset(dfn,0,sizeof(dfn));
		bridge = vDCC = id = top = 0;
		for (int i=0;i<n;++i)
			if (!dfn[i]) tarjan(i,-1);
		printf("%d %d\n",bridge,vDCC);
	}
	return 0;
}

刷题是一种出路,枚举是一种思想,打表是一种勇气,搜索是一种信仰,剪枝是一种精神,骗分是一种日常。——来自高一大佬

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/84673297
今日推荐