概率+树规 熟练剖分

这里写图片描述
这里写图片描述

根节点不一定是1,但是是一个确定的点,看谁不是儿子就行了。。
这道题我们考虑从儿子推到根。设f[i][j]表示以i为根的子树中,最长轻链长度为j的概率。
因为每一个son被选为重儿子的概率相同,且重儿子对父亲贡献和轻儿子不同,所以要每一个点为重儿子,之后挨个枚举每个儿子。这个效率是N^2,然后要枚举链的长度,如果枚举到size[root],相当于N^3,废掉了。。但只要枚举到size[son]+1就好了。更大不会再有意义,概率一定为零。
在计算答案时要再次用到f数组,所以要先存一下,再转过去。
对于每一次枚举重儿子时枚举所有儿子时的计算方法。g[i][j]表示i节点子树中最长链长为0~j的概率之和。
f[i][j]=f[son][j]*g[i][j]+g[son][j]*f[i][j]-f[i][j]*f[son][j];(对于重儿子)
考虑一次向答案上添加一个子节点。那么父节点最长为j的链出现地方有两种可能。1,出现在之前已经添加到答案中的子节点里(f[i][j]),那么这时新添加的子节点的长度只要在0~j中哪一个都无所谓了。同理,最长j出现在新添加的节点中,那之前添加的多长也就无所谓了。。。
那么对于轻儿子呢?不同于重儿子,轻儿子与父亲的连边会对答案造成贡献,所以只要把son的j改成j-1即可。
最后统计答案:根节点子树中最长轻链长为j的概率*j,加个和就好了。
对于除法,乘逆元即可。
注:最好别看我代码,巨丑,还难理解。。。我把f和g数组合二为一了。。。。

#pragma GCC optimize("O3")
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 3005
#define mod 1000000007
#define ll long long
using namespace std;
int read()
{
    int sum=0,f=1;char x=getchar();
    while(x<'0'||x>'9'){if(x=='-')f=-1;x=getchar();}
    while(x>='0'&&x<='9'){sum=(sum<<1)+(sum<<3)+x-'0';x=getchar();}
    return sum*f;
}
struct road{int v,next;}lu[N*2];
int n,e,adj[N];
ll out[N],sz[N],g[N],h[N],f[N][N];
bool v[N];
inline ll cheng(ll x,int m)
{
    ll ans=1;
    while(m)
    {
        if(m&1)ans=ans*x%mod;
        x=x*x%mod;
        m/=2;
    }
    return ans;
}
inline void dfs(int x)
{
    sz[x]=1;
    for(int i=adj[x];i;i=lu[i].next)
    {
        dfs(lu[i].v);
        sz[x]+=sz[lu[i].v];
    }
    ll fm=cheng(out[x],mod-2);
    for(int i=adj[x];i;i=lu[i].next)
    {
        int zz=lu[i].v;
        for(int j=0;j<=n;j++)g[j]=1;
        for(int j=adj[x];j;j=lu[j].next)
        {
            int to=lu[j].v;
            for(int k=0;k<=sz[to]+1;k++)
            {
                ll s=g[k],sum=f[to][k];
                if(k)s-=g[k-1],sum-=f[to][k-1];
                if(s<0)s+=mod;if(sum<0)sum+=mod;
                if(to==zz)
                    h[k]=((sum*g[k]+s*f[to][k]-sum*s)%mod+mod)%mod; 
                else if(k)
                {
                    sum=f[to][k-1];if(k!=1)sum-=f[to][k-2];
                    if(sum<0)sum==mod;
                    h[k]=((sum*g[k]+s*f[to][k-1]-sum*s)%mod+mod)%mod;
                }
            }
            g[0]=h[0];h[0]=0;
            for(int k=1;k<=sz[to]+1;k++)g[k]=(g[k-1]+h[k])%mod,h[k]=0;
        }
        for(int j=sz[x];j>=1;j--)g[j]=(g[j]-g[j-1]+mod)%mod;
        for(int j=0;j<=sz[x];j++)f[x][j]=(f[x][j]+g[j]*fm%mod)%mod;
    }
    if(!adj[x])f[x][0]=1;
    for(int i=1;i<=n;i++)f[x][i]=(f[x][i]+f[x][i-1])%mod;
}
int main()
{
    //freopen("tree.in","r",stdin);
    //freopen("tree.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int k=read();out[i]=k;
        for(int j=1;j<=k;j++)
        {
            int x=read();v[x]=1;
            lu[++e]=(road){x,adj[i]};adj[i]=e;
        }
    }
    int root;
    for(int i=1;i<=n;i++)if(!v[i])root=i;
    dfs(root);
    ll ans=0;
    for(ll i=1;i<=n;i++)
        ans=(ans+i*(f[root][i]-f[root][i-1]+mod)%mod)%mod;
    cout<<ans;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值