题意
给出n,k,m,问有多少个序列组
(A0,A1,...,An)
(
A
0
,
A
1
,
.
.
.
,
A
n
)
满足以下条件:
序列
Ai
A
i
的长度恰好为
i
i
所有元素均在的范围内
Ai−1
A
i
−
1
是
Ai
A
i
的子序列
Ai
A
i
的字典序大于
Ai−1
A
i
−
1
答案模
m
m
输出。
分析
对于一个长度为
n
n
的序列,考虑如何在某个元素左边插入一个新元素,使得新序列的字典序大于原来的序列。
假设把放在某个数
y
y
的左边,那么需要满足两个条件中的其中一个:
2)x=y
2
)
x
=
y
且在该位置后面第一个不等于
x
x
要小于
但不难发现第二个条件其实是没用的,因为若
y=x
y
=
x
,那么我们在
y
y
的左边插入和在
y
y
后面第一个不等于的数左边插入
x
x
,得到的序列都是一样的。
所以我们构造新序列就只能在某个小于的数的左边插入
x
x
。
考虑按照如下方法对一个序列组构造一棵树:
每个点有两个信息:标号和权值。
初始时有一个节点,标号和权值均为0。
当我们在的基础上构造
Ai
A
i
时,设在某个数
p
p
左边插入了,新建一个点,标号为
i
i
,权值为。设
p
p
是第次被插入,则新节点的父亲为标号为
r
r
的点。
这样我们就把一个序列组和一棵树唯一对应了起来,接下来我们只要统计满足条件的树的数量。
不难发现这棵树需要满足任意节点的标号和权值都需要严格大于其父亲的标号和权值。
那么我们就可以开始dp了。
设表示一棵大小为
n
n
的树,满足上述条件且根节点的权值为的方案。
枚举编号为
1
1
的节点所在子树的大小,可以得到
用前缀和优化转移可以做到 O(kn2) O ( k n 2 ) 。
代码
#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;
}