AGC24 E - Sequence Growing Hard-树形DP

传送门

题意:

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

Solution:

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

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

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

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

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

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

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

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

f[i][j]=i1siz=1Csiz1i2f[isiz][j]kp=j+1f[siz][p] 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(n2k) 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]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值