[AGC024] E - Sequence Growing Hard 树型DP

把问题看成每次向序列中添加一个 [1,K] [ 1 , K ] 的数,假设我们要填的数是 x x ,因为要保证字典序变大,那么所填的位置往后的第一个非x数必须 <x < x <script type="math/tex" id="MathJax-Element-80"> x x 中的任意位置再填一个x都是等价的,所以等同于只能在一个 <x < x <script type="math/tex" id="MathJax-Element-83"> x x
那我们把填一个数看成一个二元组(t,w),表示在第 t t 次填了数w,假设把这个 w w 填的位置后面的那个<w的数看成它的父亲,那么就有了一个树型结构。(可以把序列末尾看成一个 (t=0,w=0) ( t = 0 , w = 0 )
那么问题就转化成了计满足一下条件的树的个数:
1. troot=0,wroot=0 t r o o t = 0 , w r o o t = 0
2. tx t x 互不相同, wx[0,K] w x ∈ [ 0 , K ]
3. tx>tfax t x > t f a x
4. wx>wfax w x > w f a x
于是我们可以设 fi,j f i , j 表示有 i i 个点,wroot=j的树的个数。考虑转移,我们可以给根节点添加一个大小为 k k wroot>j的子树,因为子树顺序的问题,我们可以钦定这棵新子树的根是所有子树的根中最先添加的,那么也就是把剩下 k1 k − 1 个元素的序列插进去组成一个 i2 i − 2 的序列(整颗树的根和该子树的根已经确定),转移方程就是:

fi,j=k=1i1(i2k1)fik,jd>jfk,d f i , j = ∑ k = 1 i − 1 ( i − 2 k − 1 ) f i − k , j ∗ ∑ d > j f k , d

后面那个求和用一下前缀和就能优化到 O(n2K) O ( n 2 K ) 了。
代码:

#include<iostream>
#include<cstdio>
#define ll long long
#define N 310
using namespace std;
ll C[N][N],f[N][N],s[N][N];
int n,K,mod;
int main()
{
    scanf("%d%d%d",&n,&K,&mod); 
    C[0][0]=1;
    for(int i=1;i<=n;C[i][0]=1,i++)
        for(int j=1;j<=i;j++)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    for(int j=0;j<=K;j++)
        f[1][j]=1,s[1][j]=K-j+1;
    for(int i=2;i<=n+1;i++)
    {
        for(int j=0;j<=K;j++)
            for(int k=1;k<i;k++)
                f[i][j]=(f[i][j]+f[i-k][j]*s[k][j+1]%mod*C[i-2][k-1])%mod;  
        for(int j=K;j>=0;j--)
            s[i][j]=(f[i][j]+s[i][j+1])%mod;
    }
    printf("%lld",f[n+1][0]);           
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值