Codeforces Round #343 (Div. 2) E. Famil Door and Roads lca 树形dp,lca

题目链接:http://codeforces.com/contest/629/problem/E
题意:给你一棵节点数为n的树,随机地在树上的任意两个点连一条边,给你m个询问,每次询问两个点,问连一条边后如果这两个点能在简单环中,简单环的期望是多少。简单环即这两个点在一个环上,这个环是没有重边的。
解法:这里先设置几个变量dep[i]:i节点的深度,这里记dep[0]=0,dep[1]=1;sz[i]:i节点的子树的节点总数;f[i][j]:i节点的2^j倍父亲;sdown[i]:i节点子树中的所有点到i节点的距离和;sall[i]:所有点到i节点的距离和;t=lca(u,v).
先考虑lca(u,v)!=u && lca(u,v)!=v的情况,想要使得u,v都在简单环中,那么连边的两个端点一定是一个在u的子树中,另一个在v的子树中,且连边的方案数为sz[u]*sz[v],那么我们得到的期望值是sdown[u]/sz[u]+sdown[v]/sz[v]+1+dep[u]+dep[v]-2*dep[t].这里dep[u]+dep[v]-2*dep[t]+1是每一个形成的简单环都有的长度,所以可以先加上去.
然后考虑lca(u,v)==u || lca(u,v)==v的情况,不妨假设lca(u,v)=v,那么连边的两个端点一端一定在u的子树中,另一端在v的上面,即树上的所有点除去不包括u这个节点的子树,我们得到的期望值是sdown[u]/sz[u]+(sall[v]-sdown[v1]-sz[v1])/(n-sz[v1]) (v1是u,v路径上v的子节点).
第一次dfs先求出sdown[i],然后第二次dfs就能求出sall[i]了.

//CF 629E

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;

int n, m, sz[maxn], dep[maxn], f[maxn][23];
long long sdown[maxn], sall[maxn];
///sdown[i]:i节点子树中的所有点到i节点的距离和;sall[i]:所有点到i节点的距离和;t=lca(u,v).
struct edge{
    int v, nxt;
    edge(){}
    edge(int v, int nxt) : v(v), nxt(nxt) {}
}E[maxn*2];

int head[maxn], edgecnt;

void addedge(int u, int v){
    E[edgecnt].v = v, E[edgecnt].nxt = head[u], head[u] = edgecnt++;
}

void dfs1(int u, int fa, int d) ///处理出dep, sz, sdown
{
    dep[u] = d;
    sz[u] = 1;
    sdown[u] = 0;
    for(int i = head[u]; ~i; i = E[i].nxt){
        int v = E[i].v;
        if(v == fa) continue;
        f[v][0] = u;
        dfs1(v, u, d+1);
        sz[u] += sz[v];
        sdown[u] += sdown[v] + sz[v];
    }
}

void dfs2(int u, int fa)///计算sall
{
    for(int i = head[u]; ~i; i = E[i].nxt){
        int v = E[i].v;
        if(v == fa) continue;
        sall[v] = sall[u] + n - 2*sz[v];
        ///这里是主要的公式,可以这样理解:所有点到父亲节点u的距离和sall[u]已经算出来了,
        ///那么算v这个节点的时候,不在v子树范围内的点到v的距离都多了1,所以加上n-sz[v],
        ///v的子树的点到v的距离都减少了1,所以要减去sz[v].
        dfs2(v, u);
    }
}

void init(){
    dep[0] = 0;
    dfs1(1, -1, 1);
    sall[1] = sdown[1];
    dfs2(1, -1);
}

int lca(int x, int y)
{
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = 20; i >= 0; i--){
        if(dep[f[x][i]] >= dep[y]){
            x = f[x][i];
        }
    }
    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 find_son(int u, int d)
{
    for(int i = 20; i >= 0; i--){
        if((1<<i) <= d){
            u = f[u][i];
            d -= (1<<i);
        }
    }
    return u;
}

int main()
{
    cin >> n >> m;
    memset(head, -1, sizeof(head));
    edgecnt = 0;
    for(int i = 1; i < n; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    init();
    for(int k = 1; k <= 20; k++){
        for(int i = 1; i <= n; i++){
            f[i][k] = f[f[i][k-1]][k-1];
        }
    }

    while(m--)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        int t = lca(u, v);
        double ans = (double)(dep[u] + dep[v] - 2*dep[t]) + 1;
        //cout << ans << endl;
        if(t == u || t == v){
            if(t == u) swap(u, v);
            int v1 = find_son(u, dep[u] - dep[v] - 1);
            long long temp = sall[v] - sdown[v1] - sz[v1];
            ans += (double)sdown[u] / (double) sz[u] + (double)(temp) / (double)(n - sz[v1]);
            printf("%.10f\n", ans);
        }
        else{
            ans += (double)(sdown[u]) / sz[u] + (double) (sdown[v]) / (double) sz[v];
            printf("%.10f\n", ans);
        }
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值