CF1540B Tree Array
给定一棵 \(n\) 个节点的树。
对于这棵树,我们通过下列方法来生成一个序列:
- 等概率选择这 \(n\) 个节点中的一个节点,并对这个节点打上标记(初始时没有节点被打上标记);
- 在所有没被打上标记且与至少一个打上标记的节点相连的节点中等概率选择一个节点并对这个节点打上标记;
- 重复步骤 2 直到所有节点都被打上标记,此时生成的序列就是所有节点编号按照节点被打上标记的时间排序后的形成的序列。
求生成序列的期望逆序对数对 \(10^9+7\) 取模后的值。
\(2\leq n\leq200;\) 给出的是棵树。
显然所有步骤 2 操作得出的点与步骤一得出的点构成一个连通块。不妨枚举每个点作为步骤 1 得到的点,让他们作为树根,分别求出期望后求和再除以 \(n\)。
对每一对可能存在逆序对求其存在的概率,概率求和即为以某个点为根的逆序对期望。即假设 \(a>b\),那么求出 \(a\) 号点比 \(b\) 号点先选择的概率
同时这个问题又等价于从 \(lca(a,b)\) 开始取点,\(a\) 比 \(b\) 先选择到的概率。
无论现在所选取的点有哪些,所有能进一步选择的点选择概率相等,更具体地,每一个状态选择两个子状态的概率相等,哪怕它有多个子状态,但我们只关心这两个。
所以我们只需 \(lca(a,b)\) 到 \(a\) 的路径上一共取多少个点和到 \(b\) 的路径上取多少个点即可推出 \(a\) 点比 \(b\) 点先取的概率。
定义 \(f_{i,j}\) 为距离 \(a\) 点 \(i\) 个单位,\(b\) 点 \(j\) 个单位,先走到 \(a\) 点的概率,\(f_{0,i}=0\)。如果我们顺推的话(直接对应加粗的结论),可以得出。
\[f_{i+1,j}\gets f_{i+1,j}+\frac{1}{2}f_{i,j}\\ f_{i,j+1}\gets f_{i,j+1}+\frac{1}{2}f_{i,j} \]或者我们认为
\[f_{i,j}=\frac{f_{i-1,j}+f_{i,j-1}}{2} \]那么代码实现就很简单了。
const int N=203;
const ll MOD=1000000007;
inline ll fpr(ll b,ll t=MOD-2,ll x=1){
for(;t;t>>=1,b=b*b%MOD)
if(t&1)x=x*b%MOD;
return x;
}
const ll INV2=fpr(2);
int n,lca[N][N],dep[N];
vector<int>edge[N];
ll f[N][N],ans;
inline vector<int> getlca(int u,int f){
dep[u]=dep[f]+1;
vector<int>lis;
for(auto v:edge[u]){
if(v==f)continue;
vector<int>tmp=getlca(v,u);
for(auto x:tmp)lis.push_back(x);
}lis.push_back(u);
for(auto x:lis){
for(auto y:lis)
if(!lca[x][y])lca[x][y]=u;
}
return lis;
}
int main(){
n=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
edge[u].push_back(v);
edge[v].push_back(u);
}
for(int i=0;i<=n;++i)f[0][i]=1;
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*INV2%MOD;
}
for(int i=1;i<=n;++i){
memset(lca,0,sizeof(lca));
getlca(i,0);
for(int j=2;j<=n;++j){
for(int k=1;k<j;++k){
int l=lca[j][k];
ans=(ans+f[dep[j]-dep[l]][dep[k]-dep[l]])%MOD;
}
}
}
printf("%lld\n",ans*fpr(n)%MOD);
return 0;
}
标签:概率,int,题解,ll,lca,Tree,lis,CF1540B,节点
From: https://www.cnblogs.com/BigSmall-En/p/16720689.html