题意:
给出n,k,m,问有多少个序列组 (A0,A1,...,An) ( A 0 , A 1 , . . . , A n ) 满足以下条件:
序列 Ai A i 的长度恰好为i
所有元素均在 [1,k] [ 1 , k ] 的范围内
Ai−1 A i − 1 是 Ai A i 的子序列
Ai A i 的字典序大于 Ai−1 A i − 1
答案模m输出。
n,k≤300 n , k ≤ 300
Solution:
我们考虑在一个长为n的序列中加入一个数x,使得新的序列的字典序要比之前的大
那么这个x只能加在某个比x小的数y的左边或者是序列末尾(如果y前有多个等于x的值,那么x加在哪里都一样)
那么我们可以把一个序列组转换成一棵树:
1.每个点有两个信息:标号和权值。
2.初始时有一个节点,标号和权值均为0。
3.当我们通过 Ai−1 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]=∑i−1siz=1Csiz−1i−2∗f[i−siz][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]);
}