题目
描述 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;
}
刷题是一种出路,枚举是一种思想,打表是一种勇气,搜索是一种信仰,剪枝是一种精神,骗分是一种日常。——来自高一大佬