先考虑一个朴素的DP:设 (f_{i,j}) 表示第 (i) 个位置填 (1sim j) 的所有升序序列对答案的贡献。转移方程:
[f_{i,j}=f_{i-1,j-1} imes j+f_{i,j-1}
]
这样时间复杂度是 (O(nk)) 的,无法接受。
我们先假设 (f_{i}(j)=f_{i,j}) 是关于 (j) 的一个多项式,次数设为 (g(i))。我们发现状态转移方程中一个差分的形式:
[f_{i,j}-f_{i,j-1}=f_{i-1,j-1} imes j
]
那么次数的方程:
[g(i)-1=g(i-1)+1
]
所以得出:
[g(i)=g(i-1)+2
]
显然 (g(0)=0),那么以上假设成立(严谨证明可使用数学归纳法):(f_i(j)) 是关于 (j) 的 (2 imes i) 次多项式。所以我们只需要计算出 (f_{i,1}sim f_{i,2n+1}) 的值,然后拉格朗日插值即可。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N=1009;
int k,n,p,f[N][N],pre[N],suf[N],fac[N],inv_fac[N];
void init()
{
scanf("%lld %lld %lld",&k,&n,&p);
}
int ksm(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=res*a%p;
b>>=1,a=a*a%p;
}
return res;
}
int calc(int x,int k)
{
pre[0]=suf[k+2]=1;
for (int i=1;i<=k+1;i++)
pre[i]=pre[i-1]*(x-i)%p;
for (int i=k+1;i>=1;i--)
suf[i]=suf[i+1]*(x-i)%p;
fac[0]=1;
for (int i=1;i<=k+1;i++)
fac[i]=fac[i-1]*i%p;
inv_fac[k+1]=ksm(fac[k+1],p-2);
for (int i=k;i>=0;i--)
inv_fac[i]=inv_fac[i+1]*(i+1)%p;
int ans=0;
for (int i=1;i<=k+1;i++)
ans=(ans+f[n][i]*pre[i-1]%p*suf[i+1]%p*inv_fac[i-1]%p*inv_fac[k+1-i]%p*((k+1-i&1)?-1:1))%p;
return ans;
}
void work()
{
if(k<=2*n+1)
{
for (int i=1;i<=k;i++)
f[1][i]=i+f[1][i-1];
for (int i=2;i<=n;i++)
for (int j=1;j<=k;j++)
f[i][j]=(f[i][j-1]+f[i-1][j-1]*j%p)%p;
int tmp=1;
for (int i=1;i<=n;i++)
tmp=tmp*i%p;
printf("%lld
",tmp*f[n][k]%p);
}
else
{
int K=2*n+1;
for (int i=1;i<=K;i++)
f[1][i]=i+f[1][i-1];
for (int i=2;i<=n;i++)
for (int j=1;j<=K;j++)
f[i][j]=(f[i][j-1]+f[i-1][j-1]*j%p)%p;
printf("%lld
",(calc(k,2*n)+p)%p*fac[n]%p);
}
}
signed main()
{
init();
work();
return 0;
}