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[i−1][j]+f[i][j−1])/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";
}