题目链接
思路
对于这种期望题第一反应就是把每一对逆序对独立出来计算他们的贡献,那么对于一对逆序对\((j,i),j>i\)就需要考虑在每一种根下面的情况。将逆序对\((j,i)\)在每一种根下面所有的情况加起来除以\(n\),就是逆序对\((j,i)\)对总期望的贡献值。要让逆序对有贡献,那么就必须要让\(j\)出现在\(i\)的前面,就是求\(j\)出现在\(i\)前面的概率。
假设现在\(i,j\)的\(LCA\)为\(p\),那么就有\(p-i\)和\(p-j\)的两条路径。我们可以发现在选择\(p-i,p-j\)这两条路径之外的点时对这个概率并无影响。所以只要考虑以\(p\)为LCA的情况下先到达\(j\)的概率。
\(f[x][y]:\)表示距离从\(p\)到\(i\)路径上还剩下x个点,\(p\)到\(j\)路径上还剩下\(y\)个点的概率值。
转移方程即为
\(f[x][y]=(f[x-1][y]+f[x][y-1])/2\)
表示可能选择的是路径\(p-i\)上的点的概率和路径\(p-j\)上的点的概率。
那么因为要计算逆序对\((j,i)\)的情况,初始情况下要设置\(f[1...n][0]=1\)。
所以就需要枚举每个根,然后再去计算这种根的情况下的概率值。最后把所有的概率值加起来/n即可。
代码
vector<int> e[N];
int fa[N][10];
LL f[N][N];
int depth[N];
int n;
LL kpow(LL a, LL n) {
LL res = 1;
while(n) {
if(n & 1) res = res * a % mod;
n >>= 1;
a = a * a % mod;
}
return res;
}
void bfs(int s) {
memset(depth, 0, sizeof depth);
queue<int> q;
q.push(s);
depth[s] = 1;
while(!q.empty()) {
int u = q.front(); q.pop();
for(auto v : e[u]) {
if(!depth[v]) {
depth[v] = depth[u] + 1;
fa[v][0] = u;
for(int i = 1; i < 9; i++) {
fa[v][i] = fa[fa[v][i - 1]][i - 1];
}
q.push(v);
}
}
}
}
int lca(int a, int b, int u) {
if(depth[a] < depth[b]) swap(a, b);
for(int i = 8; i >= 0; i--) {
if(depth[fa[a][i]] >= depth[b]) {
a = fa[a][i];
}
}
if(a == b) return a;
for(int i = 8; i >= 0; i--) {
if(fa[a][i] != fa[b][i]) {
a = fa[a][i];
b = fa[b][i];
}
}
return fa[a][0];
}
LL calc(int u) {
memset(fa, 0, sizeof fa);
depth[u] = 1;
bfs(u);
LL res = 0;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
int p = lca(i, j, u);
res = (res + f[depth[i] - depth[p]][depth[j] - depth[p]]) % mod;
}
}
return res;
}
void solve() {
scanf("%d", &n);
for(int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
e[u].push_back(v);
e[v].push_back(u);
}
for(int i = 1; i <= n; i++) {
f[i][0] = 1;
}
LL tmp = kpow(2, mod - 2);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
f[i][j] = (f[i - 1][j] + f[i][j - 1]) % mod * tmp % mod;
}
}
LL res = 0;
for(int i = 1; i <= n; i++) {
res = (res + 1LL * calc(i) * kpow(n, mod - 2) % mod) % mod;
}
printf("%lld\n", res);
}