题意:给出一个无相图,然后q次新增加边,问在添加边的过程中桥的数目
当且仅当无向边(u,v)为树枝的时候,需要满足dfn(u)<low(v),
也就是v向上翻不到u及其以上的点,那么u-v之间一定能够有1条或者多条边不能删去,因为他们之间有一部分无环,是桥
思路:首先我们知道在给定一张图之后,不断添加边,桥的数目只会减少而不是增加
tarjan的使用就是缩点,将一个连通分量缩成一个点,那么那个连通分量中的边都不会是桥
同时缩完点之后我们就会发现,桥其实就是新形成的树的边
在添加的过程中,如果是在连通分量内就不会减少桥的数目
如果是u,v在两个不同的连通分量中,那么u,v以及其lca(最小公共祖先)就会连成一个回路,就形成了一个新的连通分量
那么被连接的这整个一段部分的所有桥的数目就会减少
完整代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <vector> #include <stack>
#define IOS ios::sync_with_stdio(0); cin.tie(0); using namespace std; typedef long long LL; typedef pair<int,int>pii; const double eps = 1e-8; const int inf = 0x3f3f3f3f; const int maxn = 1e5+10; const int max_edge = 4e5+5; struct Edge { int to, next; }edge[max_edge]; int top, head[maxn]; int pre[maxn]; int id, dfn[maxn], low[maxn]; int cnt; int bridge[maxn]; int n,m; void add(int u,int v){ edge[top].to = v; edge[top].next = head[u]; head[u] = top++; } void tarjan(int x,int f){ //更新时间戳 dfn[x] = low[x] = ++id; for(int i = head[x]; ~i;i = edge[i].next){ int v = edge[i].to; if(!dfn[v]){ tarjan(v,x); pre[v] = x; low[x] = min(low[v],low[x]); //桥的判定条件 if(low[v]>dfn[x]){ bridge[v] = 1; cnt++; } } else if(v != f){ low[x] = min(low[x],dfn[v]); } } } void init(){ memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(bridge,0,sizeof(bridge)); memset(head,-1,sizeof(head)); cnt = id = top = 0; } void lca(int u,int v){ //进入时交换一下,把根赋值给u if(dfn[u] < dfn[v]) swap(u,v); //先将底的一边走到同样的高度 while(dfn[u] > dfn[v]){ if(bridge[u]) cnt--,bridge[u]=0; u = pre[u]; } //两边同时向lca走 while(u != v){ if(bridge[u]) cnt--,bridge[u] = 0; if(bridge[v]) cnt--,bridge[v] = 0; u = pre[u]; v = pre[v]; } } int main(){
//IOS (加速cin可以少一倍时间) int Case = 0; while(cin>>n>>m&&n&&m){ init(); for(int i =0;i<m;i++){ int u,v; cin>>u>>v; add(u,v),add(v,u); } tarjan(1,1); int q; cin>>q; cout<<"Case "<<++Case<<":"<<endl; while(q--){ int u,v; cin>>u>>v; lca(u,v); cout<<cnt<<endl; } } return 0; }