传送门

题意:

给出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]);
}