题解:
- 首先说一下,一模一样的题目在HDU和POJ上得到的结果与众不同。HDU在vector记录边AC,在POJ上用C++和G++不是TLE就是RE。最后改为邻接矩阵,C++1400ms左右AC,G++WA。
- 这一题要求桥,这和求个割点有一点不一样。如果(u,v)是桥,u是父节点,v是子节点,那么我们用v来记录桥。因为子节点有唯一的父节点,唯一确定一座桥。并且不用特殊考虑根节点,因为根节点没有父节点,显然不会记录桥。
- 在求割点时,lowlink[v] >= dfn[u],u为割点,但是这里不能取等号。如果取等号,显然(u,v)不是桥了,已经形成一个环了。
- 最后如果加上(u,v)这一条边,那么会形成一个环,我们找到u,v的LCA,这些路径上的边都不会形成桥了,打上标记即可。
代码:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
using namespace std;
int const M = 400000 + 10;
int const N = 100000 + 10;
int n,m,cnt,scc,root,T,sum;
int dfn[N],lowlink[N],sccno[N],bridge[N],fa[N];
int first[N],ne[M],to[M],tot;
stack<int>st;
void add(int u,int v){
ne[++tot] = first[u];
to[tot] = v;
first[u] = tot;
}
bool Init(){
scanf("%d%d",&n,&m);
if(!n && !m) return false;
memset(first,0,sizeof(first));
tot = 0;
for(int i=0;i<m;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
return true;
}
void dfs(int u,int f){
dfn[u] = lowlink[u] = ++cnt;
st.push(u);
for(int i=first[u];i;i=ne[i]){
int v = to[i];
if(v == f) continue;
if(!dfn[v]){
fa[v] = u;
dfs(v,u);
lowlink[u] = min(lowlink[u],lowlink[v]); //不用考虑根结点
if(lowlink[v] > dfn[u]) bridge[v] = true, ++sum; //子节点唯一确定一条路径
}else if(!sccno[v]){
lowlink[u] = min(lowlink[u],dfn[v]);
}
}
if(dfn[u] == lowlink[u]){
scc++;
while(1){
int x = st.top(); st.pop();
sccno[x] = scc;
if(x == u) break;
}
}
}
void Tarjan(){
scc = cnt = sum = 0;
while(!st.empty()) st.pop();
memset(dfn,0,sizeof(dfn));
memset(sccno,0,sizeof(sccno));
memset(bridge,false,sizeof(bridge));
dfs(1,0);
}
int LCA(int u,int v){
int ret = 0;
if(dfn[u] < dfn[v]) swap(u,v);
while(dfn[u] > dfn[v]){
if(bridge[u]){
ret++;
bridge[u] = false;
}
u = fa[u];
}
while(dfn[v] > dfn[u]){
if(bridge[v]){
ret++;
bridge[v] = false;
}
v = fa[v];
}
return ret;
}
void solve(){
printf("Case %d:\n",++T);
int q;
scanf("%d",&q);
for(int i=0;i<q;i++){
int u,v;
scanf("%d%d",&u,&v);
int ret = LCA(u,v);
sum -= ret;
printf("%d\n",sum);
}
printf("\n");
}
int main(){
while(Init()){
Tarjan();
solve();
}
return 0;
}