codeforces 728 div2 D - Tree Array
思路:
对于本题,我们考虑,如果以任何一个点为根,都会生成一颗不同的树,逆序对的数量就可能有变化,所以我们考虑枚举根节点。然后选定此时的根节点x,我们只需要枚举求出任何两个点对总答案的贡献,就可以求出期望。
假设此时我们选定根节点为x,此时枚举到的两个点a, b(a > b):如果a是b的祖先,那么a在最终排列中的位置一定在b的前面,所以此时对答案的贡献是1n\frac{1}{n}n1(选x做根节点的概率为(1n\frac{1}{n}n1))。如果b是a的祖先,那么贡献就是0。
最后还剩下一种情况,假定根节点为x,此时a,b两个点(a>b)有公共祖先c,且c≠a&c≠bc\not=a \& c \not = bc=a&c=b。题目中介绍了,每一次要等概率地从未标记的点中选一个点,保证这个点和标记的点中的任何一点要有一条边相连。它的意思是这样:

比如我们按着某一种情况,已经标记了1点,和2点,此时下一个可选点,可以标记3,4,5三个点,下一次选择每一个可选点的概率是13\frac{1}{3}31。而不应当这么理解:我们此时有12\frac{1}{2}21的概率选1的邻接点,此时有12\frac{1}{2}21的概率选2的邻接点,假设选了1的邻接点,各有12\frac{1}{2}21的概率选3,4点,假设选了2的邻接点,那么就有1的概率继续选择5点。按着这样理解,有12\frac{1}{2}21的概率选5,各14\frac{1}{4}41的概率选3,4。这样理解,显然不是题目中的意思。
如果按着正确的题意来看,a,b两个点(a>b)有公共祖先c,且c≠a&c≠bc\not=a \& c \not = bc=a&c=b的情况下,出现逆序对的贡献,只和这条a到b的简单路径有关,和路径之外的点都没有任何关系,因此此时的贡献只和a到c的路径长度,b到c的路径长度有关,如果a到c的路径长度是定的,b到c的路径长度是定的,那么经过每次等可能的选择出现逆序对的概率就是定的。
所以我们要进行一个预处理,设dp[i][j]表示i=dis(a,lca(a,b)),j=dis(b,lca(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])/2f[i][j] = (f[i-1][j] + f[i][j-1])/2f[i][j]=(f[i−1][j]+f[i][j−1])/2。
初始条件f[0][i]=1,f[i][0]=0f[0][i] = 1, f[i][0] = 0f[0][i]=1,f[i][0]=0。
所以本题就做完了,我认为理解题目中的每次等可能地选取是很重要的,同时要把答案拆分成每一对点的贡献,并且要观察出,贡献只和两个点之间的简单路径有关,通过概率dp预处理,让程序复杂度在O(n3logn)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";
}
本文详细解析 CodeForces 728 Div.2 D 题目,通过枚举根节点和利用概率DP预处理的方法,将程序复杂度控制在 O(n^3logn),成功解决该问题。
233

被折叠的 条评论
为什么被折叠?



