AGC24 E - Sequence Growing Hard-树形DP

传送门

题意:

给出n,k,m,问有多少个序列组 ( A 0 , A 1 , . . . , A n ) 满足以下条件:
序列 A i 的长度恰好为i
所有元素均在 [ 1 , k ] 的范围内
A i 1 A i 的子序列
A i 的字典序大于 A i 1
答案模m输出。
n , k 300

Solution:

我们考虑在一个长为n的序列中加入一个数x,使得新的序列的字典序要比之前的大

那么这个x只能加在某个比x小的数y的左边或者是序列末尾(如果y前有多个等于x的值,那么x加在哪里都一样)

那么我们可以把一个序列组转换成一棵树:

1.每个点有两个信息:标号和权值。
2.初始时有一个节点,标号和权值均为0。
3.当我们通过 A i 1 构造 A i 时,设在某个数y左边插入了x,新建一个点,标号为i,权值为x。设y的标号为p,那么i的父亲是p

这样就可以把一个序列组和一棵树对应起来

答案即为符合上面条件的树的数量

f [ i ] [ j ] 表示当前的树有i个节点,根的权值为j,所能产生的合法的树的个数

转移时枚举加一棵大小为siz的子树连接根节点:

f [ i ] [ j ] = s i z = 1 i 1 C i 2 s i z 1 f [ i s i z ] [ j ] p = j + 1 k f [ s i z ] [ p ]

组合数的意义是:固定了根节点和子树的根节点,我们需要在剩下的i-2个数中选择siz-1个当做子树

加上前缀和优化,复杂度为 O ( n 2 k )

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,k,mod;
int C[310][310],f[310][310],s[310][310];
int main()
{
    scanf("%d%d%d",&n,&k,&mod);
    C[0][0]=1;
    for (int i=1;i<=n;i++)
    {
        C[0][i]=1;
        for (int j=1;j<=i;j++)
            C[j][i]=(C[j-1][i-1]+C[j][i-1])%mod;
    }
    for (int i=0;i<=k;i++) f[1][i]=1,s[1][i]=i;
    for (int i=2;i<=n+1;i++)
        for (int j=0;j<=k;j++)
        {
            int ans=0;
            for (int siz=1;siz<=i-1;siz++)
                ans=(1ll*C[siz-1][i-2]*f[i-siz][j]%mod*(s[siz][k]-s[siz][j]+mod)%mod+ans)%mod;
            f[i][j]=ans;
            s[i][j]=ans;
            if (j) s[i][j]=(ans+s[i][j-1])%mod;
        }
    printf("%d",f[n+1][0]);
}

猜你喜欢

转载自blog.csdn.net/charlie_jilei/article/details/80401251