题目
Problem Description
有一天TMK在做计算机网络的实验,然后他用模拟系统建造了一个网络,这个网络形状就是一棵树,接着TMK随机选了两个不同的点(这两个点有可能本来就直接相连),然后给这棵网络添加了一条线,TMK吃饭回来,发现电脑蓝屏了,但tmk还记得添加了一条边后的图是经过了两个点x,y,但是具体怎么样就忘记了,tmk想知道添加了一条边后的图的环的长度期望值是多少。
Input
第一行有两个数,n,m,表示树的点个数还有询问的个数
接下来的n-1行,每行两个数字ai,bi表示原来的树上的一条边
接下来的m行,每行两个数字下x,y,表示一个询问,当点x和点y在环上的时候,这个环的期望长度是多少。每个询问相互独立2<=n,m<=100000,
1<=ai,bi<=n
1<=x,y<=n,x!=yOutput
每个询问输出一行,表示询问的答案,要求误差不超过1e-6
Samples
No. Input Output 1 4 3
2 4
4 1
3 2
3 1
2 3
4 14.00000000
3.00000000
3.000000002 10 10
2 6
4 2
5 1
1 3
6 9
7 10
6 10
8 5
6 5
2 10
1 5
9 3
3 7
3 4
2 7
4 5
7 2
6 4
10 24.00000000
4.25000000
5.00000000
6.00000000
6.00000000
4.50000000
5.00000000
4.50000000
4.50000000
4.00000000
分析
- 题目大意就是给一棵树,每次询问两个点,加一条边后使得这两个点在一个环上,每次询问输出这个环长度的期望值。
- 我们给这棵树定一个总的根节点。
- 可以想出,若加了一条边后这两个点在一个环上,那么这条边的两个端点肯定在这两个点为根的子树里(若这两个点其中一个为另一个的祖先会稍微麻烦一点,思考一下即可)
- 于是稍微做一下,处理好一些子树的信息,再推出算式分类求出答案即可。
程序
#include <cstdio>
#define For(x) for(int h=head[x],o=V[h]; h; o=V[h=to[h]])
#define add(x,y) (to[++num]=head[x],head[x]=num,V[num]=y)
int head[100005],to[200005],V[200005],num;
int n,m,A,B,K,dp[100005],f[100005][30];
long long up,down,ds[100005],sz[100005],Ds[100005];
/*
sz[x]: 子树 x 的大小(包括 x )
ds[x]: 子树 x 内的点到 x 的距离和
Ds[x]: 子树 x 外的点到 x 的距离和
dp[x]: 节点 x 的深度(deep的缩写)
*/
void dfs(int x,int Fa){
f[x][0]=Fa;
dp[x]=dp[Fa]+1;
sz[x]=1;
For(x) if (o!=Fa){
dfs(o,x);
sz[x]+=sz[o];
ds[x]+=ds[o]+sz[o];
}
}
void Dfs(int x,int Fa){
For(x) if (o!=Fa){
Ds[o]+=Ds[x]+n-sz[x]+1;
Ds[o]+=ds[x]-ds[o]-sz[o]+sz[x]-sz[o]-1;
Dfs(o,x);
}
}
void getf(){
for (int j=1; j<=20; j++)
for (int i=1; i<=n; i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int father(int x,int y){ //x的 y 代祖先
for (int i=20; i>=0; i--) if (y&(1<<i)) x=f[x][i];
return x;
}
int LCA(int x,int y){
if (dp[x]>dp[y]) x=father(x,dp[x]-dp[y]);
if (dp[x]<dp[y]) y=father(y,dp[y]-dp[x]);
if (x==y) return x;
for (int i=20; i>=0; i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int main(){
freopen("1.txt","r",stdin);
scanf("%d%d",&n,&m);
for (int i=1,u,v; i<n; i++){
scanf("%d%d",&u,&v);
add(u,v), add(v,u);
}
dfs(1,1);
Dfs(1,1);
getf();
for (int i=1; i<=m; i++){
scanf("%d%d",&A,&B); if (dp[A]>dp[B]) K=B,B=A,A=K; //dp[A]<=dp[B]
K=LCA(A,B);
if (K==A){ //A是B的祖先
int G=father(B,dp[B]-dp[K]-1);
down=(n-sz[G])*sz[B];
up=(Ds[A]+ds[A]-ds[G]-sz[G])*sz[B];
up+=ds[B]*(n-sz[G]);
up+=down*(dp[B]-dp[A]+1);
printf("%.8f\n",(double)up/(double)down);
}
else{ //A不是B的祖先
down=sz[A]*sz[B];
up=ds[A]*sz[B]+ds[B]*sz[A];
up+=down*(dp[A]-dp[K]+dp[B]-dp[K]+1);
printf("%.8f\n",(double)up/(double)down);
}
}
return 0;
}
提示
要注意,打 LCA 中预处理 ST 表时,应该先枚举 j(2^j),在枚举 i,因为每个 f[i][j] 的递推公式(f[i][j]=f[f[i][j-1]][j-1]
)中 j-1 肯定是要先处理好的。