递推dp 超级树

这里写图片描述
设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 long
using 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;
}
posted @ 2017-10-06 20:19  Hzoi_QTY  阅读(176)  评论(1编辑  收藏  举报