题目大意:https://www.cnblogs.com/Juve/articles/11186805.html

题解:

先给出官方题解:

其实这题跟期望没什么关系,因为E=$\sum_\limits{x=0}^{+\infty}$p(x)*x,所以我们只要求出轻链最多为 i 的概率就行了。
以下把题面所求的精彩操作称为最长轻链。而这个东西显然是可以由子节点转移到父亲节点的。
F[i][j]表示在点 i 为根的子树中,向下最长轻链长度为 j 的概率。
对于一个点,先枚举它选择的重儿子是谁,然后扫一遍它的所有儿子,让 G[i][j]=$\sum_\limits{k}^{k<=j}$F[i][k],假如当前扫的儿子是 x(x 是重儿子)。
F[i][j]=F[x][j]*G[i][j]+G[x][j]*F[i][j]-F[x][j]*F[i][j]-----(1)
不是重儿子的需要相应的改一下,还有要注意 F 数组更新的顺序,标程是先把 F 暂存到了一个别的数组里。
转移的时候如果(1)式子的 j 循环到了 size[i],那么复杂度可以被卡到 N3,我们发现当 j>size[x]+1 的时候 F[x][j]=0,G[x][j]=1,F[i]相当于没有变,所以只要 j 循环到 size[x]+1 就行了。
每个节点只有在 dp 它父亲时会被枚举成为重儿子,然后最多把整棵树的大小扫一遍,所以复杂度为N2.

这看起来极其难以理解,为了便于理解,我粘了Al_Ca大佬的题解,楷体字是我的一些理解

f[i][j]表示在点 i 为根的子树中,向下最长轻链长度小于等于 j 的概率。g[x][k]表示x节点之前的儿子中最长轻链长度(包括x)小于等于k的概率,但当前节点的g与其他节点没有关系,第一维可以清空,所以只有g[k]

首先递归下去并求出子树大小,然后枚举重儿子,枚举该点最长轻链长度,再次枚举儿子节点并逐个考虑,

假设当前枚举的重儿子是v(i),枚举到儿子节点v(j),x最长轻链长度为k,设gs为v(j)之前考虑的儿子中最长轻链长度为k的概率(因为是前缀和,所以代码中有减这个操作,f同理),如果v(j)=v(i)即v(j)为重儿子,则设fs为以v(j)为根的子树最长轻链长度为k的概率,f[x][k]=gs*f[v(j)][k](v(j)之前考虑的儿子为长度k*以v(j)为根字数长度<=k(此条边为重链所以可以等于))+fs*g[k]-gs*fs(去重),

stop,我们看一下这个方程,当v(j)为重儿子时,有:

f[x][k]=gs*f[v(j)][k]+fs*g[k]-gs*fs,解释一下

在点 x 为根的子树中,向下最长轻链长度小于等于 k 的概率可以有这样几种转移:

1:在v(j)之前考虑的儿子中最长轻链长度为k 且同时 以v(j)为根的子树向下最长轻链长度小于等于k。

  若v(j)为x的重儿子,则v(j)到x的路径没有贡献,所以是小于等于k。

2:以v(j)为根的子树最长轻链长度为k 且同时 v(j)父节点之前的儿子中最长轻链长度(包括父节点)小于等于k

3:我们发现有情况算重了,那就是同时满足 在v(j)之前考虑的儿子中最长轻链长度为k 和 以v(j)为根的子树最长轻链长度为k,所以应减去

如果v(j)是轻儿子,则设fs为以v(j)为根的子树最长轻链长度为k-1的概率,f[x][k]=gs*f[v(j)][k-1]+fs*g[k]-gs*fs,大致同上,

stop,再解释一下gs*f[v(j)][k-1]含义

和上一个转移方程一样,只不过这时的v(j)不是重儿子,所以v(j)和它的父节点之间的边会有贡献,所以这时转移的应是以v(j)为根的子树向下最长轻链长度小于等于k-1。

剩下的就都一样了

只是x与v(j)相连的这条边为轻链所以有减1,值得提醒的一点是这里的f[x][k]并不是最终的f[x][k],只是考虑到当前几个儿子时的值,一个儿子一个儿子地向里加。考虑到f数组直接改的话会错,所以用h数组保存,最后加到g数组中清空h,当v(i)为重儿子这个情况考虑玩后将g数组加到f中去,清空g。当前节点x求完后,此时的f数组并不是前缀和,所以需要再次转化。

最后求答案时再次将前缀和转化为单个的值。

代码实现还是有一定难度的,具体细节看代码吧:

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 3005
#define ll long long
using namespace std;
const int mod=1e9+7;
ll n,in_deg[MAXN],root,son_num[MAXN],ans=0;
ll to[MAXN],nxt[MAXN],pre[MAXN],cnt;
void add(ll u,ll v){
cnt++,to[cnt]=v,nxt[cnt]=pre[u],pre[u]=cnt;
}
ll q_pow(ll a,ll b,ll p){
ll ans=1;
for(;b;b>>=1){
if(b&1) ans=ans*a%p;
a=a*a%p;
}
return ans;
}
ll size[MAXN],g[MAXN],h[MAXN],f[MAXN][MAXN];//f[i][j]表示在点 i 为根的子树中,向下最长轻链长度小于等于 j 的概率,h表示临时的f数组,g[x][i]表示x节点之前的儿子中最长轻链长度(包括x)小于等于k的概率,但第一维可以随时清空,所以只有g[i]
void dfs(ll x){
size[x]=1;
for(ll i=pre[x];i;i=nxt[i]){
ll y=to[i];
dfs(y),size[x]+=size[y];
}
ll q=q_pow(son_num[x],mod-2,mod);
for(ll i=pre[x];i;i=nxt[i]){//枚举重儿子
for(ll j=0;j<=n;j++) g[j]=1;
ll h_son=to[i];//heavy son
for(ll j=pre[x];j;j=nxt[j]){//枚举其他儿子
ll a_son=to[j];//another son
for(ll k=0;k<=size[a_son]+1;k++){
ll gs=g[k],fs=f[a_son][k];//gs为a_son之前考虑的儿子中最长轻链长度为k的概率
if(k) gs-=g[k-1],fs-=f[a_son][k-1];
if(a_son==h_son) h[k]=(gs*f[a_son][k]%mod+fs*g[k]%mod-fs*gs%mod+mod)%mod;//若a_son为重儿子,fs为以a_son为根的子树最长轻链长度为k的概率
else{
fs=f[a_son][k-1];if(k>1) fs-=f[a_son][k-2];//若a_son为轻儿子,则设fs为以a_son为根的子树最长轻链长度为k-1的概率
h[k]=(gs*f[a_son][k-1]%mod+fs*g[k]%mod-gs*fs%mod+mod)%mod;
}
}
g[0]=h[0],h[0]=0;
for(ll k=1;k<=size[a_son]+1;k++) g[k]=(g[k-1]+h[k])%mod,h[k]=0;
}
for(ll j=size[x];j>=1;j--) g[j]=(g[j]-g[j-1]+mod)%mod;
for(ll j=0;j<=size[x];j++) f[x][j]=(f[x][j]+g[j]*q%mod)%mod;
}
if(!pre[x]) f[x][0]=1;
for(ll i=1;i<=size[x]+1;i++)
f[x][i]=(f[x][i]+f[x][i-1])%mod;
return ;
}
int main(){
scanf("%lld",&n);
for(ll i=1;i<=n;i++){
scanf("%lld",&son_num[i]);
for(ll j=1,son;j<=son_num[i];j++){
scanf("%lld",&son);
add(i,son);
in_deg[son]++;
}
}
for(ll i=1;i<=n;i++){
if(!in_deg[i]){
root=i;
break;
}
}
dfs(root);
for(ll i=1;i<=n;i++)
ans=(ans+i*(f[root][i]-f[root][i-1]+mod)%mod)%mod;
printf("%lld\n",ans);
return 0;
}

HZOI2019熟练剖分(tree)的更多相关文章

  1. 熟练剖分(tree) 树形DP

    熟练剖分(tree) 树形DP 题目描述 题目传送门 分析 我们设\(f[i][j]\)为以\(i\)为根节点的子树中最坏时间复杂度小于等于\(j\)的概率 设\(g[i][j]\)为当前扫到的以\( ...

  2. 20210501 序列,熟练剖分(tree),建造游乐园(play)

    考场 \(65+5+0\),并列 rk2 最高分 \(55+10+10\) T1:等比数列可以写作 \(q^kx\),发现 \(q\le1000\) 且有一档分为 \(a_i\le100\),想到 \ ...

  3. 【模拟7.14】B. 熟练剖分(tree) (概率DP)

    一道概率神题,考试时没读清题考完看了学长的玄学题解看了好几个小时 首先f[i][j]表示在点 i 为根的子树中,向下最长轻链长度小于等于 j 的概率. 首先递归下去并求出子树大小,然后枚举重儿子,枚举 ...

  4. NOIP模拟测试3「序列·熟练剖分·建造游乐园(play)」

    ---恢复内容开始--- 序列 刚调出来样例就A了,假装是水题. 因为是乱序,我们要求出来每两项之间最小公比,而不是直接比 求出来每两项之间最小公比,然后扫一遍就完了.(还要注意重复情况) 那么问题就 ...

  5. BZOJ 2758 Blinker的噩梦(扫描线+熟练剖分+树状数组)

    题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2758 题意:平面上有n个多边形(凸包和圆).任意两个多边形AB只有两种关系:(1) ...

  6. BZOJ 3672 [Noi2014]购票 (熟练剖分+凸壳维护)

    题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3672 题意:给出一棵有根树(1为根),边有长度.每个点u有三个属性(len[u], ...

  7. [***]HZOI20190714 T2熟练剖分

    这题真的神仙,蒟弱表示看题解看不懂……std看了几个小时大概看懂,还有一些细节的东西没有思考. 最难受的是题解和std好像并不是同一个人写的……数组状态不一样……看了好久才看出来f也是前缀和. F[i ...

  8. Luogu2420 让我们异或吧 (熟练剖分)

    \(dis[u] \bigoplus dis[v] = dis[u] \bigoplus dis[v] \bigoplus dis[lca\{x,y\}] \bigoplus dis[lca\{x,y ...

  9. 高二小假期集训—D5

    刚调完了一个非常恶心的题(可能是我写的太恶心了),心累……先写会博客吧. 今天上午该完了考试的三道题,感觉第二道真的是个好题(学长说是经常会遇到的一类题……完了完了),看了一个小时std才看懂,写了篇 ...

随机推荐

  1. 解析Mybatis入门第三天

    目的:使用mybatis对数据的一些标签的使用和表与表之间的一对多和多对一的查询方式. 例如:if.where.foreach 前言:同样是使用idea创建一个普通的maven工程(如何创建一个普通的 ...

  2. luoguP1029 最大公约数和最小公倍数问题 [gcd][数论]

    题目描述 输入二个正整数x0,y0(2<=x0<100000,2<=y0<=1000000),求出满足下列条件的P,Q的个数 条件: 1.P,Q是正整数 2.要求P,Q以x0为 ...

  3. python使用PIL处理图片后返回给前端的坑

    一.python代码 这里有个坑,之前没有将bytes图片数据转成base64就返回到前端了,但在前端处理的时候,怎么都显示不出图片来,虽然数据拿到了,但bytes被传到前后变str了,所以怎么搞都没 ...

  4. 第五篇:zTree节点的一些操作,权当备份

    项目场景:将zTree的一个节点挪到某个已知的根节点下,因为树上的节点都是数据库查询出来的,所以不能直接用addNodes()这个方法(否则一刷新又恢复原样了),而是要把这个节点及其某些属性数据保存到 ...

  5. Java程序员必备的10个大数据框架!

    作者:java妞妞 blog.csdn.net/javaniuniu/article/details/71250316 当今IT开发人员面对的最大挑战就是复杂性,硬件越来越复杂,OS越来越复杂,编程语 ...

  6. Spring Boot 发布 jar 包转为 war 包秘籍。

    Spring Boot是支持发布jar包和war的,但它推荐的是使用jar形式发布.使用jar包比较方便,但如果是频繁修改更新的项目,需要打补丁包,那这么大的jar包上传都是问题.所以,jar包不一定 ...

  7. 17.splash_case02

    # 抓取<我不是药神>的豆瓣评论 import csv import time import requests from lxml import etree fw = open('doub ...

  8. 使用Math.random()函数生成n到m间的随机数字

    使用js生成n到m间的随机数字,主要目的是为后期的js生成验证码做准备,Math.random()函数返回0和1之间的伪随机数 讲解: 本文讲解如何使用js生成n到m间的随机数字,主要目的是为后期的j ...

  9. 在 /proc 里实现文件

    所有使用 /proc 的模块应当包含 <linux/proc_fs.h> 来定义正确的函数. 要创建一个只读 /proc 文件, 你的驱动必须实现一个函数来在文件被读时产生数据. 当 某个 ...

  10. Entity Framework Code First 模式-建立一对多联系

    一.建立一对多联系 使用的例子为Product与Category,一个种类(Product)对应多个商品(Product) 1.外键列名默认约定 在“一”这边的实体增加一个集合属性(public vi ...