题目大意:一个公园里面有n个景点,建设了m条道路,现在该建设者想知道如果为每个环形的路线选择旅游团,是否可能有的路会发生旅游团之间的争吵。争吵的 定义是:两个旅游团出现在了同一条路径上,即存在两个环形路线部分道路重叠。并且他还想知道有哪些道路是不需要建设的,一条道路不在任何环形路线上的称之 为不需要建设的路线。你的任务是输出不需要建设的道路的数量以及旅游团可能会发生争吵的道路的数量。
思路:显然不必要的边肯定是桥,这个毋庸置疑。
另外就是发生冲突的边,建设者弄得每个路线都是一个环,也就是说路径上的点没有重复的。
那么我们这时候要怎么判有没有边是重复的呢,首先要明确几个定义:对于一个连通图来说,如果任意两个点至少存在两条点不重复的路径,那么这个图就是点双连通的,而边双连通则是边不重复的路径,这个定义先放在这里。
其次,一个环肯定是一个点双连通,因为它符合定义,但是点双连通不一定是环,它里面可能有很多环,当一个点连通里有好多环的时候它里面的每一条边都一定是冲突边,这个画个图看一下就行了,我不会证明。
那么就是判断点双连通里有几个环,如果一个点双连通点个数等于边的个数那么它就是一个环,不用管,如果一个点双连通点个数小与边的个数,则说明至少有三个环(随便加一条边就有三个环),则所有的边都是冲突边。
其实桥也是一个点双连通分支,但是它的点数大于边数。
这样就可以计算出所有的冲突边数量了。
那为什么要用点双连通分支而不是边双连通分支呢?
我个人的理解:边双连通分支里的某些环是可以没有冲突的,那你在一个边连通分支里是没办法搞清楚究竟有那些边是冲突的,而在一个块里如果有多个环那么肯定它的每条边都被至少两个环所公用。
如下图:
这整个就是个边连通分量,但是右边的环与左边的没有冲突,没办法判断冲突边有多少个。但是如果是点连通分量,那么上面就被分成了两个块,这样就可以判断了。
另外,从环的定义来说,是它从一个点回到另一个点是不经过重复的点的,那么边连通分量的话是做不到里面全是环的,例如上图最左边的点到最右边的点再回去,这个就不是一个环,事实是左边块的点都不能去到右边块里的点然后形成环的,那么你把右边的那个环跟其它的环放在一起是没有意义的。
代码:
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<set>
#include<map>
#include<vector>
#include<cstring>
#include<stack>
#include<cmath>
#include<queue>
using namespace std;
#define M 200004
#define N 10004
struct Edge
{
int from,to,nex;
} edge[M*2];
int head[N],edgenum;
void add(int u, int v)
{
Edge E = {u,v,head[u]};
edge[edgenum] = E ;
head[u]=edgenum++;
Edge E2= {v,u,head[v]};
edge[edgenum] = E2;
head[v]=edgenum++;
}
int DFN[N],dfs_clock,block;
int Belong[N];
bool iscut[N];
int bridge;
// 割顶的bccno无意义
stack<Edge> S;
vector<int>G[N];
bool vis[10005];
int ans_clash;
void clash(int b){
int cnt=0;
memset(vis,false,sizeof(vis));
for(int i=0;i<G[b].size();i++){
vis[G[b][i]]=true;
}
for(int i=0;i<G[b].size();i++){
int u=G[b][i];
for(int j=head[u];j!=-1;j=edge[j].nex){
if(vis[edge[j].to])
cnt++;
}
}
cnt>>=1;
if(cnt>G[b].size()){
ans_clash+=cnt;
}
return;
}
int Tarjan(int u,int fa)
{
int lowu, child=0;
lowu = DFN[u] = ++dfs_clock;
for(int i=head[u]; ~i; i = edge[i].nex)
{
int v = edge[i].to;
Edge e;
e.from=u,e.to=v;
if(!DFN[v])
{
S.push(e);
child++;
int lowv=Tarjan(v,u);
lowu=min(lowu,lowv);
if(lowv>=DFN[u])
{
if(lowv>DFN[u]){
bridge++;
}
iscut[u]=1;
block++;
G[block].clear();
while(1)
{
Edge x=S.top();
S.pop();
if(Belong[x.from]!=block)
{
G[block].push_back(x.from);
Belong[x.from]=block;
}
if(Belong[x.to]!=block)
{
G[block].push_back(x.to);
Belong[x.to]=block;
}
if(x.from==u&&x.to==v)break;
}
clash(block);
}
}
else if(DFN[v]<DFN[u]&&v!=fa)
{
S.push(e);
lowu=min(lowu,DFN[v]);
}
}
if(fa<0&&child==1) iscut[u]=0;
return lowu;
}
void solve(int l, int r)
{
memset(DFN,0,sizeof(DFN));
memset(iscut,0,sizeof(iscut));
memset(Belong,0,sizeof(Belong));
dfs_clock=block=bridge=0;
ans_clash=0;
for(int i=l; i<=r; i++) if(!DFN[i])Tarjan(i,-1);
}
void init()
{
memset(head, -1, sizeof head);
edgenum = 0;
}
int main(){
int n,m;
int a,b;
while(scanf("%d%d",&n,&m)&&(n+m)){
init();
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
add(a,b);
}
solve(0,n-1);
printf("%d\n",block);
printf("%d %d\n",bridge,ans_clash);
}
return 0;
}