题目大意:
n ( n ≤ 200 ) n(n\leq200) n(n≤200)个节点的树。初始的时候,等概率随机选择一个点标记,接来随机选择一个与标记点相连的未标记点来标记,直到所有的点都被标记。根据点被标记的顺序,生成一个数列。这个数列的逆序对的期望个数为多少个?
解题思路:
- 我们先可以对每一个逆序对进行考虑?
- 比如我们考虑 a ≤ b a\leq b a≤b就是 a a a出现在 b b b后面的概率 P b a P_{ba} Pba是多少?
- 我们先固定一个节点为根去考虑,设 L C A LCA LCA是 a a a和 b b b的最近公共祖先,那么概率 P b a P_{ba} Pba只和 a , b a,b a,b简单路径上面的点有关 !!
- 为什么呢?
- 对于固定的根节点,我们每次都会得到一个点集的排列,但是我们发现,除了 a a a和 b b b路径上的点之外,其他的点可以出现在任意的合理的排列,那么我们实际上是不是只考虑 a , b a,b a,b路径上的点就好了
- 现在设 x = d i s t ( L C A , a ) , y = d i s t ( L C A , b ) x=dist(LCA,a),y=dist(LCA,b) x=dist(LCA,a),y=dist(LCA,b)那么就相当于从里面两个桶分别有 x , y x,y x,y个球,去拿球,且 b b b最先被拿到的概率
- 那么就有 d p [ i ] [ j ] dp[i][j] dp[i][j]为第一个桶有 i i i个求,第二个桶有 j j j个求,且第二个桶先被拿完的概率。
- 转移计算 d p [ i ] [ j ] = 1 2 ( d p [ i − 1 ] [ j ] × d p [ i ] [ j − 1 ] ) dp[i][j]=\frac{1}{2}(dp[i-1][j]\times dp[i][j-1]) dp[i][j]=21(dp[i−1][j]×dp[i][j−1])
- 那么思路就很明了了,就是枚举根节点,计算 L C A LCA LCA枚举点对,去计算每个逆序对出现的概率,累加就好了
- 我们可以先预处理出 d p dp dp数组
- L C A LCA LCA倍增算就可以了
AC code
O ( n 3 l o g n ) O(n^3logn) O(n3logn)
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 7;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
read(first);
read(args...);
}
ll dp[202][202], ans;
int n, fa[202][21], depth[202];
vector<int> G[202];
ll qim(ll a, ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res % mod;
}
inline void dfs(int u, int f) {
fa[u][0] = f;
depth[u] = depth[f]+1;
for(int i = 1; i < 20; ++ i)
fa[u][i] = fa[fa[u][i-1]][i-1];
for(auto it : G[u]) {
if(it == f) continue;
dfs(it,u);
}
}
inline int lca(int u, int v) {
if(depth[u] < depth[v]) swap(u,v);
int delta = depth[u] - depth[v];
for(int i = 19; i >= 0; -- i)
if(delta >> i & 1)
u = fa[u][i];
if(u == v) return v;
for(int i = 19; i >= 0; -- i)
if(fa[u][i] != fa[v][i])
u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
inline void slove(int root) {
ms(fa,0);
ms(depth,0);
dfs(root,0);
for(int i = 1; i <= n; ++ i)// 枚举点对
for(int j = 1; j <= i - 1; ++ j) {
int LCA = lca(i, j);
ans = (ans + dp[depth[i] - depth[LCA]][depth[j] - depth[LCA]]) % mod;
}
}
inline void init() {
for(int i = 0; i <= n; ++ i) dp[0][i] = 1; // 边界条件,我这里写的和上面的x,y反了过来,第一维是大数
ll inv2 = qim(2,mod-2);
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;
}
int main() {
IOS;
cin >> n;
for(int i = 1; i < n; ++ i) {
int u, v;
cin >> u >> v;
G[v].push_back(u);
G[u].push_back(v);
}
init();
for(int i = 1; i <= n; ++ i) slove(i);// 枚举root
cout <<ans*qim(n,mod-2)%mod;
return 0;
}