递推dp 超级树

来源:互联网 发布:靠谱韩国代购淘宝店 编辑:程序博客网 时间:2024/04/30 10:48

这里写图片描述
设f[i][j],i表示i-超级树,j表示树上同时存在j条路径且无重复的点。
首先得解释明白数组含义。。。
第二维的目的很单纯:把路径合并时不会走重复的点,不会走重复的边。
那么说一下转移,
设sum=f[i-1][l]*f[i-1]*r;

  1. 什么也不往上加 f[i][l+r]+=sum;
  2. 只把根节点自己加上去 f[i][l+r+1]+=sum
  3. 把根节点和两棵子树中某一条路径连起来 f[i][l+r]+=sum*2*(l+r){要考虑双向,左右子树各自枚举}
  4. 把根节点和一左一右两条路径连起来 f[i][l+r-1]+=sum*2*l*r
  5. 把根节点和两个左(或右)子树路径连起来 f[i][l+r-1]+=sum*2*(l*(l-1)+r*(r-1))

    转移大概就是这样了,那么来考虑初始化和输出,
    初始化:f[1][0]=f[1][1]=1;
    输出:f[k][1],树上只有一条路径且自己上面无重复的点的总方案数。
    那么再来想,f[k][1]只能有f[k-1][2]转移而来。依次类推,第二维只需要存最大到k个的方案数就行了(记得和总节点数量比一下,防TLE,博主死于此)
    O(k^3)

#include<cstdio>#include<cstdlib>#include<cstring>#include<iostream>#include<algorithm>#define ll long longusing namespace std;ll n,mod,f[305][305];int main(){//  freopen("tree.in","r",stdin);//  freopen("tree.out","w",stdout);    scanf("%lld%lld",&n,&mod);    f[1][0]=f[1][1]=1;    for(int i=2;i<=n;i++)    {        ll k=n-i+2;        if(i<=9)k=min(k,(ll)1<<(i-1));        for(ll l=0;l<=k;l++)            for(ll r=0;r<=k;r++)                if(l+r<=n)                {                    ll h=f[i-1][l]*f[i-1][r]%mod;                    f[i][l+r]+=h;                               if(f[i][l+r]>=mod)f[i][l+r]-=mod;                    f[i][l+r+1]+=h;                             if(f[i][l+r+1]>=mod)f[i][l+r+1]-=mod;                    f[i][l+r]+=2*h*(l+r)%mod;                    if(f[i][l+r]>=mod)f[i][l+r]-=mod;                    f[i][l+r-1]+=2*h*(l*r%mod)%mod;              if(f[i][l+r-1]>=mod)f[i][l+r-1]-=mod;                    f[i][l+r-1]+=h*(l*(l-1)%mod+r*(r-1)%mod)%mod;if(f[i][l+r-1]>=mod)f[i][l+r-1]-=mod;                }    }    cout<<f[n][1]%mod;}
原创粉丝点击