题目链接
题目描述
网络管理员管理大型网络。该网络由N台计算机和成对计算机之间的M链路组成。任何一对计算机都通过连续的链接直接或间接连接,因此可以在任何两台计算机之间转换数据。管理员发现某些链接对网络至关重要,因为任何一个链接的故障都可能导致某些计算机之间无法转换数据。他把这种联系称为桥梁。他计划逐一添加一些新链接以消除所有桥梁。 您将通过在添加每个新链接后报告网络中的网桥数来帮助管理员。
输入
输入包含多个测试用例。每个测试用例以包含两个整数N(1≤N≤100,000)和M(N-1≤M≤200,000)的行开始。 以下M行中的每一行包含两个整数A和B(1≤A≠B≤N),表示计算机A和B之间的链接。计算机编号从1到N.保证任何两台计算机都连接在一起最初的网络。 下一行包含一个整数Q(1≤Q≤1,000),这是管理员计划逐个添加到网络的新链接数。 以下Q行的第i行包含两个整数A和B(1≤A≠B≤N),这是连接计算机A和B的第i个新链接。 最后一个测试用例后跟一行包含两个零的行。
输出
对于每个测试用例,打印一行包含测试用例编号(以1开头)和Q行,其中第i行包含一个整数,表示添加第一个i新链接后网络中的网桥数。在每个测试用例的输出后打印一个空行。
思路:题目中提到了桥的概念,可以anjan的的判桥,也可以缩点后生成的DAG上的边都是桥(点的个数-1)。
用一种解法是直接暴力利用LCA的思路进行标记边,但是我认为会T,但实际上不会T。
我看到了另外一种解法,就是在树上进行并查集合并(具体思路看代码),我的大概思路和网上基本一直一致,但我的代码不是T就是WA。
自己掉进的坑:
① 在缩点后加了双向边,导致每条边都重复假如了,导致T了。因为在构建原图的时候,已经是双向边,在缩点后只需要加边一次就可以。(单向边Tanjan缩点写多了。。。。。。)
② 在进行树上并查集的时候,每个点都和自己父亲结点合并,保证每个点的根都在本身的上方。
附上代码
///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const int mod=1e9+7;
const int INF=1e9;
int n,m,op;
int sum;
struct node
{
int e;
int p;
}load[400005],edge[400005];
int sign;
int head[100005]; ///缩点前
int rehead[100005]; ///缩点后
int grand[100005]; ///在缩点后DAG中每个点的父亲结点
int depth[100005]; ///每个点所在的深度
int dfn[100005],low[100005],time;
int stack_[100005],instack[100005],top;
int belong[100005],cnt;
void add_edge(int s,int e) ///缩点前加边
{
load[++sign]=node{e,head[s]};
head[s]=sign;
}
void add(int s,int e) ///缩点后加边
{
edge[++sign]=node{e,rehead[s]};
rehead[s]=sign;
}
void tanjan(int s,int pre)
{
dfn[s]=low[s]=++time;
stack_[++top]=s;
instack[s]=1;
int vis=0;
for(int i=head[s]; i!=-1; i=load[i].p)
{
int e=load[i].e;
if(!dfn[e])
{
tanjan(e,s);
low[s]=min(low[s],low[e]);
}
else if(pre==e) ///判断反边
{
if(vis) ///如果反边不只出现一次,说明出现重边
low[s]=min(low[s],dfn[e]);
vis++;
}
else
{
if(instack[e])
low[s]=min(low[s],dfn[e]);
}
}
int now;
if(dfn[s]==low[s])
{
cnt++;
do
{
now=stack_[top--];
instack[now]=0;
belong[now]=cnt;
}
while(now!=s);
}
return ;
}
int dfs(int s,int pre)
{
///将DAG构成一棵树,方便使用LCA
int e;
for(int i=rehead[s]; i!=-1; i=edge[i].p)
{
e=edge[i].e;
if(e!=pre)
{
depth[e]=depth[s]+1;
grand[e]=s;
dfs(e,s);
}
}
}
int p[100005];
int find(int x)
{
return p[x]==x?x:p[x]=find(p[x]);
}
void lca(int s,int e)
{
while(s!=e)
{
///每个点的P数组只能向上走(即深度越来越小)
///将本身和父亲合并
if(depth[s]>depth[e])
{
sum--;
p[s]=find(grand[s]);
s=p[s];
}
else if(depth[e]>depth[s])
{
sum--;
p[e]=find(grand[e]);
e=p[e];
}
else
{
sum--;
sum--;
p[s]=find(grand[s]);
p[e]=find(grand[e]);
s=p[s];
e=p[e];
}
}
}
void init()
{
sign=0;
time=top=cnt=0;
for(int i=0; i<=n; i++)
{
rehead[i]=head[i]=-1;
depth[i]=0;
p[i]=grand[i]=i;
dfn[i]=0;
}
}
int main()
{
int s,e,u=0;
while(scanf("%d %d",&n,&m)!=EOF,n+m)
{
init();
for(int i=1; i<=m; i++)
{
scanf("%d %d",&s,&e);
add_edge(s,e);
add_edge(e,s);
}
tanjan(1,-1);
sign=0;
for(int i=1; i<=n; i++)
{
s=i;
for(int j=head[i]; j!=-1; j=load[j].p)
{
e=load[j].e;
if(belong[s]!=belong[e])
add(belong[s],belong[e]); ///只需要加一次边,因为前面建立的就是双向边
}
}
dfs(1,-1);
scanf("%d",&op);
printf("Case %d:\n",++u);
sum=cnt-1;
while(op--)
{
scanf("%d %d",&s,&e);
///直接从该点的根开始
s=find(belong[s]);
e=find(belong[e]);
lca(s,e);
printf("%d\n",sum);
}
printf("\n");
}
return 0;
}