GalaxyOJ-745 (LCA)

探讨在一棵树形结构中,通过添加一条边形成环,并计算特定两点间环的长度期望值。涉及树的遍历、子树信息处理及环长度期望计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

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!=y

Output

每个询问输出一行,表示询问的答案,要求误差不超过1e-6

Samples

No.InputOutput
14 3
2 4
4 1
3 2
3 1
2 3
4 1
4.00000000
3.00000000
3.00000000
210 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 2
4.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 肯定是要先处理好的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值