题意
给出n,k,m,问有多少个序列组(A0,A1,...,An)(A0,A1,...,An)满足以下条件:
序列AiAi的长度恰好为ii
所有元素均在的范围内
Ai−1Ai−1是AiAi的子序列
AiAi的字典序大于Ai−1Ai−1
答案模mm输出。
分析
对于一个长度为nn的序列,考虑如何在某个元素左边插入一个新元素,使得新序列的字典序大于原来的序列。
假设把放在某个数yy的左边,那么需要满足两个条件中的其中一个:
2)x=y2)x=y且在该位置后面第一个不等于xx要小于
但不难发现第二个条件其实是没用的,因为若y=xy=x,那么我们在yy的左边插入和在yy后面第一个不等于的数左边插入xx,得到的序列都是一样的。
所以我们构造新序列就只能在某个小于的数的左边插入xx。
考虑按照如下方法对一个序列组构造一棵树:
每个点有两个信息:标号和权值。
初始时有一个节点,标号和权值均为0。
当我们在的基础上构造AiAi时,设在某个数pp左边插入了,新建一个点,标号为ii,权值为。设pp是第次被插入,则新节点的父亲为标号为rr的点。
这样我们就把一个序列组和一棵树唯一对应了起来,接下来我们只要统计满足条件的树的数量。
不难发现这棵树需要满足任意节点的标号和权值都需要严格大于其父亲的标号和权值。
那么我们就可以开始dp了。
设表示一棵大小为nn的树,满足上述条件且根节点的权值为的方案。
枚举编号为11的节点所在子树的大小,可以得到
f[n,x]=∑y=x+1kf[n−k,x]∗f[k,y]∗Cl−1n−2f[n,x]=∑y=x+1kf[n−k,x]∗f[k,y]∗Cn−2l−1
用前缀和优化转移可以做到O(kn2)O(kn2)。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
typedef long long LL;
const int N=305;
int n,k,MOD,f[N][N],s[N][N],c[N][N];
int main()
{
scanf("%d%d%d",&n,&k,&MOD);
c[0][0]=1;
for (int i=1;i<=n;i++)
{
c[i][0]=1;
for (int j=1;j<=i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
}
for (int i=0;i<=k;i++) f[1][i]=1,s[1][i]=k-i+1;
for (int i=2;i<=n+1;i++)
{
for (int j=0;j<=k;j++)
for (int l=1;l<i;l++)
(f[i][j]+=(LL)f[i-l][j]*s[l][j+1]%MOD*c[i-2][l-1]%MOD)%=MOD;
for (int j=k;j>=0;j--) s[i][j]=(s[i][j+1]+f[i][j])%MOD;
}
printf("%d",f[n+1][0]);
return 0;
}