【GDOI模拟】富爷说是一棵树

26 篇文章 0 订阅
22 篇文章 0 订阅

Description

富爷说来一棵树,于是大头栽了一棵树。树大了,有n个点和n - 1条边,任意两个点都是联通的,点的标号为1 - n。爱树的大头和富爷在树上安居乐业,但大头住在u,而富爷住在v,他们都很不高兴,因为u到v有且只有一条简单路径。
当然了,树王富爷找到了解决办法,他打算带着大头再给树建一条边(保证不是自环),而且他们会在n * (n - 1) / 2的方案中随机选择一种。
但,要让富爷和大头开心是有条件的。只有新建边之后,富爷去大头家以及大头去富爷家存在两条路径不会走相同的边时,他们才会呵呵(也就是说 存在一个简单环包含u和v)。
不开心的事情选择忘记。当富爷和大头开心时,你能得到愉快值等于环的大小。所以,你要告诉富爷和大头,当他们开心时(只考虑在环内),他们的期望愉悦值。

Solution

这也是一眼的水题啊。
其实就是求所有符合的环的大小和除以情况数。
我们只用考虑每条边的情况就好了。
对于u和v的lca不是u或v的情况(没有一个是另一个的祖先):那么答案很好求。u中每条边的贡献都是当前下面子树大小 * v的子树大小。那么把所有u的边的贡献加起来就是u的子树到u的距离和 * v的子树大小,然后u到lca和v到lca的很容易统计。
当(有一个是另一个的祖先时),设v是u的祖先:那么现在设u到v上v的儿子是son,那么很显然是用(所有节点到v的距离和-son的子树到son的距离和)*u的子树大小就是v上面的边的贡献,之后的都很显然了。
注意要开long long

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define rep(i,a) for(i=first[a];i;i=next[i])
using namespace std;
typedef long long ll;
const int maxn=100007;
int i,j,k,l,t,n,m,u,v,son;
double ans;
int first[maxn*2],next[maxn*2],last[maxn*2],num;
ll suo[maxn],size[maxn],he[maxn],deep[maxn];
int f[maxn][21];
void add(int x,int y){
    last[++num]=y,next[num]=first[x],first[x]=num;
}
int lca(int x,int y){
    int i;if(deep[x]<deep[y])swap(x,y);son=x;
    fod(i,20,0)if(deep[f[x][i]]>deep[y])x=f[x][i];
    if(deep[x]>deep[y])son=x,x=f[x][0];
    fod(i,20,0)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    if(x!=y){son=x;return f[x][0];}return x;
}
void dfs(int x,int y){
    int i;
    deep[x]=deep[y]+1,f[x][0]=y;size[x]=1;
    rep(i,x){
        if(last[i]!=y){
            dfs(last[i],x);
            size[x]+=size[last[i]];
            he[x]+=he[last[i]]+size[last[i]];
        }
    }
}
void dfs1(int x,int y){
    int i;
    rep(i,x){
        if(last[i]!=y){
            suo[last[i]]=suo[x]+n-size[last[i]]-size[last[i]];
            dfs1(last[i],x);
        }
    }
}
int main(){
//  freopen("fan.in","r",stdin);
    scanf("%d%d",&n,&m);
    fo(i,1,n-1){
        scanf("%d%d",&k,&l);
        add(k,l),add(l,k);
    }
    dfs(1,0);
    suo[1]=he[1];
    dfs1(1,0);
    fo(j,1,20)fo(i,1,n)f[i][j]=f[f[i][j-1]][j-1];
    while(m--){
        ans=0;
        scanf("%d%d",&u,&v);
        if(deep[u]<deep[v])swap(u,v);
        int o=lca(u,v);
        if(o==v){
            ans=1.0*((suo[v]-he[son]-size[son])*size[u]+(n-size[son])*he[u]+
            (deep[u]-deep[v]+1)*(n-size[son])*size[u])/((n-size[son])*size[u]*1.0);
            printf("%.8f\n",ans);
        }
        else{
            ans=he[u]*size[v]+he[v]*size[u]+(deep[u]+deep[v]-deep[o]*2+1)*size[u]*size[v];
            ans=ans/(size[u]*size[v]*1.0);
            printf("%.8f\n",ans);
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值