树的解构 题解

bsoj7076,没找到出处...

树的解构

Mivik 喜欢 Eprom 的解构俱乐部,于是他想解构一棵树。

Mivik 找到了一棵以 1 为根的有 n 个结点的有根外向树。Mivik 会进行 (n1) 次操作,每次 Mivik 都会从未删掉的边中等概率选择一条边将其删去。记这条边为 ab ,则删去这条边的代价是删边时 b 的子树大小(包括 b 自己);删去这条边后 b 为根的子树会形成一棵新的以 b 为根的有根树。

Mivik 想知道,他进行这 (n1) 次操作后期望的代价总和是多少。由于 Mivik 不喜欢太大的数,你只需要输出期望的值对 109+7 取模的结果。

对于所有测试点,满足 1n2×106 。保证给出的有根树合法。

一道并不是特别难但没有切掉的期望题。

考场写了链过后就一直在想怎么拆树,无果而终。然后想到解决排列问题的一个常见的思路是考虑所有排列方案的贡献和,最后乘个 1(n1)! 就可以了。尝试在树形dp边的排列顺序,同样无果而终。

对整体dp不好做,那就转而思考每个点对答案的贡献。

考虑从树上某个深度(到根需经过的边数)为 du 的点 u ,其向上到根的路径上各边的相对删除顺序可以用一个阶为 du 的排列 P 表示。若只考虑该路径,设此时对答案的贡献为 fu(P) ,我们尝试化简 Pperm(du)fu(P) 。(其中 perm(n) 表示所有 n 阶排列的集合)

对于其中某条位置为 i 的边,若其被删除时点 u 可对答案产生贡献,则必须保证删除该边时该边与 u 仍然连通,也就是对于任意位置为 jj<i 的的边,有 Pj>Pi 。换句话说,若从后往前 (从上至下)决定边的删除时间,那么一条删除时间为 x 的边必须在 [1,x] 中最后被选中才能满足上述条件。显然其发生概率为 1x ,故在所有排列中 x 产生贡献的次数为 du!x 。因此

Pperm(du)fu(P)=x=1dudu!x

P 的相对顺序已经确定,而在整棵树中还有其他边的相对顺序未被决定,因此点 u 的总贡献还要再乘上对 P 消序后的全部排列方案数

(n1)!du!x=1dudu!x=(n1)!x=1du1x

整合所有点的贡献并乘上概率就是最终的期望

ans=1(n1)!u=1n(n1)!x=1du1x=u=1nx=1du1x

意外的约掉了好多玩意儿呢

线性求逆元并前缀和即可 O(n) 解决。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<map>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
ll Rd(){
int ans=0;bool fh=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') fh=1; c=getchar();}
while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar();
if(fh) ans=-ans;
return ans;
}
typedef vector<ll> Vec;
typedef vector<ll>::iterator IVec;
#define foreach(it,v) for(IVec it=v.begin();it!=v.end();it++)
const ll MOD=1E9+7;
#define _ %MOD
ll PMod(ll x){
if(x<0) return x+MOD;
else if(x>=MOD) return x-=MOD;
else return x;
}
const ll MXN=2E6+5;
ll N;Vec chd[MXN];
ll dep[MXN];
void InfoDFS(ll u,ll depth){
dep[u]=depth;
foreach(it,chd[u]) InfoDFS((*it),depth+1);
}
ll inv[MXN],sInv[MXN];
void SpawnInv(){
inv[1]=1;for(ll i=2;i<=N;i++) inv[i]=PMod(-(MOD/i)*inv[MOD%i]_);
sInv[1]=inv[1];for(ll i=2;i<=N;i++) sInv[i]=PMod(sInv[i-1]+inv[i]);
}
void Solve(){
SpawnInv();
InfoDFS(1,0);
ll ans=0;
for(ll u=1;u<=N;u++) ans=PMod(ans+sInv[dep[u]]);
printf("%lld",ans);
}
int main(){
//freopen("deconstruct.in","r",stdin);
//freopen("deconstruct.out","w",stdout);
N=Rd();
for(ll i=2;i<=N;i++) chd[Rd()].push_back(i);
Solve();
return 0;
}

2020/11/27

posted @   sun123zxy  阅读(178)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示