题目
解说
思路一眼就能看出来:圆方树+(LCA)。但是真正写起来是真的恶心,好多的细节,调了一晚上……
圆方树和找(LCA)没什么可说的,板子往上一套即可。主要要说的是求两点间有几个必须经过的点。
首先很显然两点间必须经过的点数量即为圆方树上两点间路径上的圆点数量,那么我们可以先(DFS)初始化出来每个点的深度,然后进行计算。
假设这两个点为(A)和(B),它们的公共祖先为(LCA),那么可以分为两种情况:(LCA)为圆点或方点,我们分别计算。
假设(LCA)为方点:
可见任意一个点到(LCA)之间的圆点数为((deep[A|B]-deep[LCA]-1)/2),(-1)是因为要去除(LCA)这个方点,除二是因为圆方树圆方点相间。
这样的话把两点到(LCA)之间的圆点数相加即可得到两点间圆点数为:((deep[A]+deep[B]-2*deep[LCA])/2-1)。
假设(LCA)为圆点:
任意一个点到(LCA)之间的圆点数为((deep[A|B]-deep[LCA])/2),因为(LCA)是圆点所以不用(-1)(当然,算出来的圆点数是包含(LCA)的)。
在把两个点相加的时候由于两边都包含了(LCA),有重复计算,所以再(-1):
((deep[A]-deep[LCA])/2+(deep[B]-deep[LCA])/2-1=(deep[A]+deep[B]-2*deep[LCA])/2-1)。
非常棒!两边的式子是一样的,都不用再判断了!
最后注意题目中说的是两条边之间的距离而非两个点之间的距离(坑死我了呜呜呜……),所以我们要枚举每个边的起点和终点计算出来经过圆点数量的最大值。
代码
#include<bits/stdc++.h>
using namespace std;
#define v e[i].to
const int maxn=10000+3,maxe=100000+3;
int n,m,tot,head[maxn],h[2*maxn],js,dfs_clock,deep[2*maxn],father[2*maxn];
int bcc_cnt,sta[maxn],top,dfn[maxn],low[maxn];
bool vis[2*maxn];
//涉及圆方树连接的记得开2倍
struct edge{
int from,to,next;
}e[2*maxe],ed[2*maxe];
void Add(int a,int b){//原图加边
e[++tot].from=a;
e[tot].to=b;
e[tot].next=head[a];
head[a]=tot;
}
void Add2(int a,int b){//圆方树加边
ed[++js].from=a;
ed[js].to=b;
ed[js].next=h[a];
h[a]=js;
}
void tarjan(int u){//求点双&构建圆方树
low[u]=dfn[u]=++dfs_clock;
sta[++top]=u;
for(int i=head[u];i;i=e[i].next){
if(dfn[v]) low[u]=min(low[u],dfn[v]);
else{
tarjan(v);
low[u]=min(low[v],low[u]);
if(low[v]==dfn[u]){
++bcc_cnt;
int temp;
do{
temp=sta[top];
top--;
Add2(n+bcc_cnt,temp);
Add2(temp,n+bcc_cnt);
}while(temp!=v);
Add2(u,n+bcc_cnt);
Add2(n+bcc_cnt,u);
}
}
}
}
void init(){
tot=0; js=0; top=0; dfs_clock=0; bcc_cnt=0;
memset(head,0,sizeof(head));
memset(e,0,sizeof(e));
memset(h,0,sizeof(h));
memset(ed,0,sizeof(ed));
memset(sta,0,sizeof(sta));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(deep,0,sizeof(deep));
memset(vis,0,sizeof(vis));
memset(father,0,sizeof(father));
memset(sta,0,sizeof(sta));
}
void dfs(int x){//预处理深度和父节点
vis[x]=1;
for(int i=h[x];i;i=ed[i].next){
int now=ed[i].to;
if(vis[now]) continue;
deep[now]=deep[x]+1;
father[now]=x;
dfs(now);
}
}
int find(int a,int b){//求LCA,别的地方太麻烦了这里懒得用倍增了……反正也超不了时
if(deep[a]<deep[b]) swap(a,b);
while(deep[a]>deep[b]) a=father[a];
while(a!=b){
a=father[a];
b=father[b];
}
return a;
}
int len(int a,int b){
int lca=find(a,b);
return (deep[a]+deep[b]-2*deep[lca])/2-1;
}
int main(){
while(1){
scanf("%d%d",&n,&m);
if(n==0&&m==0) return 0;
init();
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);
Add(y,x);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n+bcc_cnt;i++) if(!vis[i]) dfs(i);
int t;
scanf("%d",&t);
while(t--){
int x,y;
scanf("%d%d",&x,&y);
int a=e[x*2].from,b=e[x*2].to,c=e[y*2].from,d=e[y*2].to; //枚举节点
printf("%d
",max(max(len(b,d),len(a,d)),max(len(b,c),len(a,c))));
}
}
}
幸甚至哉,歌以咏志。