递推dp 超级树
设f[i][j],i表示i-超级树,j表示树上同时存在j条路径且无重复的点。
首先得解释明白数组含义。。。
第二维的目的很单纯:把路径合并时不会走重复的点,不会走重复的边。
那么说一下转移,
设sum=f[i-1][l]*f[i-1]*r;
- 什么也不往上加 f[i][l+r]+=sum;
- 只把根节点自己加上去 f[i][l+r+1]+=sum
- 把根节点和两棵子树中某一条路径连起来 f[i][l+r]+=sum*2*(l+r){要考虑双向,左右子树各自枚举}
- 把根节点和一左一右两条路径连起来 f[i][l+r-1]+=sum*2*l*r
把根节点和两个左(或右)子树路径连起来 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;
}