CodeForces - 805F - Expected diameter of a tree 树的直径+并查集

题目链接

题意:有一个由 n n n 个节点, m m m 条边构成的森林,现在有 q q q 组询问,每组询问 v , u v,u vu 表示随机从两个节点所属的树上分别选一个节点,两个节点连边,使两棵树合并成一棵新树,若能构成新树(即两个节点属于同一棵树),求新树的直径的期望值是多少,否则输出 − 1 -1 1

思路:
先通过并查集,求出每个点所属的树。
根据题目内容提供的思路,新的树的直径其实就是 m a x max max ( v v v 所属的树的直径, u u u 所属的树的直径, i i i 为起点的原树上最长路径 + j j j 为起点的原树上最长路径 + 1),其中 i i i 属于 v v v 所属的树上的节点,则 j j j 对应的是 u u u 上的。那么我们可以先预处理求出森林中所有树的直径,再预处理求出每个节点作为起点在所属树上的最长路径。
首先,我们先固定 i i i 的值,那么就是要在另一棵树上快速求出所有 j j j 的贡献。这里,就可以通过把另一棵树上所有节点为起点的最长路径长度从小到大排序,那么对于与 i i i 贡献相加再加 1 1 1 之后不小于原来两棵树的直径的最大值的,正常算贡献,若小于的,则两个点合起来的贡献则变为两棵树直径的最大值,那么可以通过二分查找这个边界条件的位置。同时对每棵树的节点的贡献维护前缀和,这样只要找到边界条件的位置,即可 O ( 1 ) O(1) O(1) 的时间求出答案。
那么我们只需要枚举其中一棵树的 i i i 即可求出这组询问的答案了。
ps:这里还要有两处优化,首先选择枚举的那棵树的节点树应该是两棵树里较少的那个。同时对于询问过的询问保存下来,之后若再次询问到则直接输出即可。

#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<cstdio>
using namespace std;
#define NUM 100005
#define ll long long
int f[NUM], n, m, q;
ll sz[NUM], td[NUM];
struct edge
{
    int to, next;
} e[NUM << 1];
int head[NUM] = {}, tot = 0, pre[NUM], top = 0, allfa[NUM];
bool flag[NUM] = {}, is_dim[NUM] = {};
vector<ll> dist[NUM], presum[NUM];
struct node
{
    int id;
    ll l;
};
queue<node> que;
map<pair<int, int>, double> check;
int gf(int x)
{
    return (f[x] == x) ? x : (f[x] = gf(f[x]));
}
inline void Union(const int &x,const int &y)
{
    int fax = gf(x);
    int fay = gf(y);
    if (sz[fax] < sz[fay])
    {
        f[fax] = fay;
        sz[fay] += sz[fax];
    }
    else
    {
        f[fay] = fax;
        sz[fax] += sz[fay];
    }
}
void dfs(const int v, const ll len, const int &s)
{
    for (int i = head[v]; i != 0; i = e[i].next)
    {
        if (pre[v] == e[i].to || is_dim[e[i].to])
            continue;
        dist[s].push_back(len);
        dfs(e[i].to, len + 1ll, s);
    }
}
inline void bfs(const node &st, node &en)
{
    node a;
    que.push(st), en = st;
    pre[st.id] = 0;
    while (!que.empty())
    {
        a = que.front(), que.pop();
        for (int i = head[a.id]; i != 0; i = e[i].next)
        {
            if (pre[a.id] == e[i].to)
                continue;
            pre[e[i].to] = a.id;
            que.push(en = node{e[i].to, a.l + 1});
        }
    }
}
inline void solve(const node &s)
{
    node st, en;
    bfs(s, st), st.l = 0, bfs(st, en);
    int i = en.id;
    ll l = 0;
    td[s.id] = en.l;
    while(i)
        is_dim[i] = true, i = pre[i];
    i = en.id;
    do
    {
        dist[s.id].push_back(max(l, td[s.id] - l));
        dfs(i, max(l, td[s.id] - l) + 1ll, s.id);
        i = pre[i], ++l;
    } while (i);
}
inline void IwantAC(const int &fv, const int &fu, const ll &maxdm)
{
    int pos;
    double ans = 0;
    for (int i = 0; i < sz[fv]; ++i)
    {
        pos = lower_bound(dist[fu].begin(), dist[fu].end(), maxdm - dist[fv][i]) - dist[fu].begin();
        ans += (maxdm * pos + presum[fu][sz[fu]] - presum[fu][pos] + dist[fv][i] * (sz[fu] - pos));
    }
    check[make_pair(fv, fu)] = ans / ((double)sz[fv] * (double)sz[fu]) + 1.0;
    printf("%.7lf\n", check[make_pair(fv, fu)]);
}
inline void AC()
{
    int v, u;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; ++i)
        f[i] = i, sz[i] = 1;
    for (int i = 1; i <= m; ++i)
    {
        scanf("%d%d", &v, &u);
        Union(v, u);
        e[++tot] = edge{v, head[u]}, head[u] = tot;
        e[++tot] = edge{u, head[v]}, head[v] = tot;
    }
    allfa[0] = 0;
    for (int i = 1; i <= n; ++i)
    {
        if(flag[gf(i)])
            continue;
        allfa[++allfa[0]] = f[i];
        solve(node{f[i], 0});
        flag[f[i]] = true;
    }
    for (int i = 1; i <= allfa[0]; ++i)
    {
        sort(dist[allfa[i]].begin(), dist[allfa[i]].end());
        presum[allfa[i]].push_back(0);
        for (int j = 1; j <= sz[allfa[i]]; ++j)
            presum[allfa[i]].push_back(presum[allfa[i]][j - 1] + dist[allfa[i]][j - 1]);
    }
    while (q--)
    {
        cin >> v >> u;
        if (gf(v) == gf(u))
        {
            puts("-1");
            continue;
        }
        if (sz[f[v]] > sz[f[u]])
            swap(v, u);
        if (check.find(make_pair(f[v], f[u])) != check.end())
            printf("%.7lf\n", check[make_pair(f[v], f[u])]);
        else
            IwantAC(f[v], f[u], max(td[f[v]], td[f[u]]) - 1ll);
    }
}
int main()
{
    AC();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值