codeforces 728 div2 D - Tree Array 讲解

codeforces 728 div2 D - Tree Array

思路:

对于本题,我们考虑,如果以任何一个点为根,都会生成一颗不同的树,逆序对的数量就可能有变化,所以我们考虑枚举根节点。然后选定此时的根节点x,我们只需要枚举求出任何两个点对总答案的贡献,就可以求出期望。
假设此时我们选定根节点为x,此时枚举到的两个点a, b(a > b):如果a是b的祖先,那么a在最终排列中的位置一定在b的前面,所以此时对答案的贡献是 1 n \frac{1}{n} n1(选x做根节点的概率为( 1 n \frac{1}{n} n1))。如果b是a的祖先,那么贡献就是0。
最后还剩下一种情况,假定根节点为x,此时a,b两个点(a>b)有公共祖先c,且 c ≠ a & c ≠ b c\not=a \& c \not = b c=a&c=b。题目中介绍了,每一次要等概率地从未标记的点中选一个点,保证这个点和标记的点中的任何一点要有一条边相连。它的意思是这样:
在这里插入图片描述
比如我们按着某一种情况,已经标记了1点,和2点,此时下一个可选点,可以标记3,4,5三个点,下一次选择每一个可选点的概率是 1 3 \frac{1}{3} 31。而不应当这么理解:我们此时有 1 2 \frac{1}{2} 21的概率选1的邻接点,此时有 1 2 \frac{1}{2} 21的概率选2的邻接点,假设选了1的邻接点,各有 1 2 \frac{1}{2} 21的概率选3,4点,假设选了2的邻接点,那么就有1的概率继续选择5点。按着这样理解,有 1 2 \frac{1}{2} 21的概率选5,各 1 4 \frac{1}{4} 41的概率选3,4。这样理解,显然不是题目中的意思。
如果按着正确的题意来看,a,b两个点(a>b)有公共祖先c,且 c ≠ a & c ≠ b c\not=a \& c \not = b c=a&c=b的情况下,出现逆序对的贡献,只和这条a到b的简单路径有关,和路径之外的点都没有任何关系,因此此时的贡献只和a到c的路径长度,b到c的路径长度有关,如果a到c的路径长度是定的,b到c的路径长度是定的,那么经过每次等可能的选择出现逆序对的概率就是定的。
所以我们要进行一个预处理,设dp[i][j]表示 i = d i s ( a , l c a ( a , b ) ) , j = d i s ( b , l c a ( a , b ) ) i = dis(a, lca(a,b)), j = dis(b, lca(a,b)) i=dis(a,lca(a,b)),j=dis(b,lca(a,b))(a > b)时候的概率。
我们可以得出 f [ i ] [ j ] = ( f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] ) / 2 f[i][j] = (f[i-1][j] + f[i][j-1])/2 f[i][j]=(f[i1][j]+f[i][j1])/2
初始条件 f [ 0 ] [ i ] = 1 , f [ i ] [ 0 ] = 0 f[0][i] = 1, f[i][0] = 0 f[0][i]=1,f[i][0]=0
所以本题就做完了,我认为理解题目中的每次等可能地选取是很重要的,同时要把答案拆分成每一对点的贡献,并且要观察出,贡献只和两个点之间的简单路径有关,通过概率dp预处理,让程序复杂度在 O ( n 3 l o g n ) O(n^3logn) O(n3logn),才能解决这个题。

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;
const ll inv2 = (mod + 1) / 2;

vector<int> adj[210];
int n;
ll dp[210][210];
int fa[210][10];
int dep[210];
ll ans = 0;
ll invn;

ll qpow(ll a, ll b){
    ll res = 1ll;
    while(b){
        if(b & 1){
            res = res * a % mod;
        }
        a = a * a % mod;
        b >>= 1;
    }
    return res % mod;
}

void dfs(int u, int par){
    dep[u] = dep[par] + 1;
    for(auto &v : adj[u]){
        if(v == par){
            continue;
        }
        fa[v][0] = u;
        for(int i = 1; i <= 8; i++){
            fa[v][i] = fa[fa[v][i-1]][i-1];
        }
        dfs(v, u);
    }
}

int lca(int u, int v){
    if(dep[u] < dep[v]){
        swap(u, v);
    }
    //离线到同一深度
    for(int i = dep[u] - dep[v], j = 0; i != 0; i >>= 1, j++){
        if(i & 1){
            u = fa[u][j];
        }
    }
    if(u == v){
        return u;
    }
    for(int i = 8; i >= 0; i--){
        if(fa[u][i] == fa[v][i]){
            continue;
        }
        u = fa[u][i];
        v = fa[v][i];
    }
    return fa[u][0];
}

void solve(int u){
    memset(dep, 0, sizeof dep);
    memset(fa, 0, sizeof fa);
    dfs(u, 0);
    // cout << u << "\n";
    for(int i = 1; i <= n; i++){
        for(int j = 1; j < i; j++){//枚举树上两个不同的点求贡献
            int k = lca(i, j);
            // cout << i << " " << j << " " << k << "\n";
            if(k == i){
                ans = (ans + invn) % mod;
            }
            else if(k == j){

            }
            else{
                ans = (ans + invn * dp[dep[i] - dep[k]][dep[j] - dep[k]] % mod) % mod;
            }
        }
    }
}

int main(){
    cin >> n;
    for(int i = 0; i <= n; i++){
        dp[0][i] = 1;
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            dp[i][j] = (dp[i-1][j] + dp[i][j-1]) * inv2 % mod;
        }
    }
    invn = qpow(1ll*n, mod-2);
    for(int i = 1, u, v; i < n; i++){
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    //枚举根节点
    for(int i = 1; i <= n; i++){
        solve(i);
    }
    cout << ans << "\n";
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值