题目链接:Click here~~
题意:
n 个点 m 条边的无向图,之后有若干次操作,问每次添加一条边后,图上还剩多少个桥边(操作是累积的)。
解题思路:
先将无向图的 边-双连通分量 缩点,缩点后重新建图,则变为一颗树,树的每条边就可以看做桥边。不妨设每次添加的边为 <u,v> 。
1> 如果之前 u,v 在同一个双连通分量里,则显然不会对结果产生影响。
2> 如果不在,相当于在树中选取两点,连了一条边,从而形成一个环,环中的点会变成一个双连通分量。对结果产生的影响取决于环上的边数。
不难发现,这个环的路径就是: u -> LCA(u,v) -> v 。
如果操作不累积,容易想到重新建图后先转化成一棵树,然后预处理出每个点的深度,离线预处理出两两的 LCA,影响就是2*dep[lca] - dep[u] - dep[v]。
但此题是操作累积的,就不能这样做了。搜了题解,大概懂别人的思路了。
首先,找 LCA(u,v) 相当于 u,v 两个点一起向上爬,直到爬到同一个点 w 即为这两点的 LCA,统计共爬了多少边。我们把这种做法叫做 climb 吧。
我们来考虑两个点爬到 LCA 后,会对之后的操作产生什么影响。
之后的点如果再次爬之前爬过的路径,不会再对结果产生影响了。
既然爬之前爬过的路径这么费力不讨好,是不是能够想到一个好的方法,让它一次登顶?
于是这点就可以很巧妙的和并查集的路径压缩联系到一起。大家看代码体会吧。
#include <stack>
#include <vector>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
template<int N,int M>
struct Graph{
int top;
struct Vertex{
int head;
}V[N];
struct Edge{
int v,next;
}E[M];
void init(){
memset(V,-1,sizeof(V));
top = 0;
}
void add_edge(int u,int v){
E[top].v = v;
E[top].next = V[u].head;
V[u].head = top++;
}
};
const int N = 1e5 + 5;
Graph<N,N*4> g,gg;
int dfn[N],low[N],belong[N];
int bccId,dfsId;
bool cutP[N],cutE[N*4];
vector<int> bcc[N];
void dfs(int pre,int u)
{
dfn[u] = low[u] = ++dfsId;
int child = 0;
for(int i=g.V[u].head;~i;i=g.E[i].next){
int v = g.E[i].v;
if(v == pre || dfn[v] > dfn[u])
continue;
if(!dfn[v]){
child++;
dfs(u,v);
low[u] = min(low[u],low[v]);
if(pre != -1 && low[v] >= dfn[u] || pre == -1 && child > 1)
cutP[u] = true;
}
else
low[u] = min(low[u],dfn[v]);
if(low[v] > dfn[u]){
cutE[i] = true;
}
}
}
void get_bcc_p(int n)
{
dfsId = bccId = 0;
memset(dfn,0,sizeof(dfn));
memset(cutP,false,sizeof(cutP));
memset(cutE,false,sizeof(cutE));
memset(belong,-1,sizeof(belong));
for(int i=1;i<=n;i++)
if(!dfn[i])
dfs(-1,i);
}
bool vis[N];
void dfs2(int u)
{
vis[u] = true;
belong[u] = bccId;
bcc[bccId].push_back(u);
for(int i=g.V[u].head;~i;i=g.E[i].next){
int v = g.E[i].v;
if(!vis[v] && !cutE[i] && !cutE[i^1])
dfs2(v);
}
}
void get_bcc_e(int n)
{
bccId = 0;
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++){
if(vis[i])
continue;
bcc[++bccId].clear();
dfs2(i);
}
}
int father[N],dep[N];
void dfss(int u,int depth)
{
vis[u] = true;
dep[u] = depth;
for(int i=gg.V[u].head;~i;i=gg.E[i].next)
{
int v = gg.E[i].v;
if(!vis[v]){
father[v] = u;
dfss(v,depth+1);
}
}
}
void rebuild(int n)
{
for(int u=1;u<=n;u++)
for(int i=g.V[u].head;~i;i=g.E[i].next){
int v = g.E[i].v;
if(belong[u] != belong[v])
gg.add_edge(belong[u],belong[v]),
gg.add_edge(belong[v],belong[u]);
}
memset(vis,false,sizeof(vis));
father[1] = 0;
dfss(1,0);
}
namespace ufSet
{
//const int N = 1e5 + 5;
int pre[N];
void init(){
memset(pre,-1,sizeof(pre));
}
int root(int x){
return pre[x] == -1 ? x : pre[x] = root(pre[x]);
}
bool gather(int a,int b){
int r1 = root(a);
int r2 = root(b);
if(r1 == r2)
return false;
else
pre[r1] = r2;
return true;
}
}
using namespace ufSet;
int climb(int a,int b){
int ret = 0;
while(1){
a = root(a);
b = root(b);
if(a == b)
break;
if(dep[a] > dep[b])
gather(a,father[a]);
else
gather(b,father[b]);
++ret;
}
return ret;
}
void debug()
{
for(int i=1;i<=bccId;i++)
for(int j=0;j<(int)bcc[bccId].size();j++)
printf("%d->%d%c",bcc[i][j],i,j==(int)bcc[bccId].size()-1?'\n':' ');
}
int main()
{
int n,m,Q,ncase = 0;
while(scanf("%d%d",&n,&m),n||m)
{
g.init();
gg.init();
ufSet::init();
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
g.add_edge(u,v);
g.add_edge(v,u);
}
get_bcc_p(n);
get_bcc_e(n);
rebuild(n);
scanf("%d",&Q);
int ans = bccId - 1;
printf("Case %d:\n",++ncase);
while(Q--)
{
int u,v;
scanf("%d%d",&u,&v);
ans -= climb(belong[u],belong[v]);
printf("%d\n",ans);
}
puts("");
}
return 0;
}