WJMZBMR的题果然放在几年后看来仍然挺神,提出了一种独特的优化DP的方式
首先我们想一个暴力DP,先定下所有数的顺序(比如强制它递增),然后最后乘上(n!)种排列方式就是答案了
那么我们容易想出一个DP,令(f_{i,j})表示前(i)个数中,最大的数小于等于(j)的方案数是多少
显然有转移:
[f_{i,j}=f_{i-1,j-1} imes j+f_{i,j-1}
]
但这样DP是(O(nA))的,需要用拉格朗日插值进行优化,不会拉格朗日插值的可以看一下浅谈拉格朗日插值
在里面就提到了插值的一个关键点就是求出给定多项式的次数。因此我们观察(f_{n,i}),猜测它是关于(i)的(t_n)次多项式
那么我们把转移方程移项一下:
[f_{i,j}-f_{i,j-1}=f_{i-1,j-1} imes j
]
有差分和乘积的一些相关性质可以得出它们之间次数的关系:
[t_n-1=t_{n-1}+1
]
而当(n=0)时,有(t_0=0),所以容易推出(t_n=2n)
因此我们知道了(f_{n,i})是关于(i)的(2n)次多项式,那么直接暴力DP求出(f_{n,1})到(f_{n,2n+1}),然后插值出(f(n,A))的值即可
CODE
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=505;
int A,n,m,f[N][N<<1],g[N<<1],fact[N<<1],mod;
inline int sum(CI a,CI b)
{
int t=a+b; return t>=mod?t-mod:t;
}
inline int sub(CI a,CI b)
{
int t=a-b; return t<0?t+mod:t;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void DP(CI n,CI m)
{
RI i,j; for (RI i=0;i<=m;++i) f[0][i]=1;
for (i=1;i<=n;++i) for (j=1;j<=m;++j)
f[i][j]=sum(1LL*f[i-1][j-1]*j%mod,f[i][j-1]);
}
inline int Lagerange(CI n,int *f,CI k)
{
if (k<=n) return f[k]; RI i; int ret=0,mt=1;
for (i=1;i<=n;++i) mt=1LL*mt*(k-i)%mod; for (i=1;i<=n;++i)
{
int tp=1LL*mt*quick_pow(k-i)%mod*f[i]%mod;
int dv=1LL*fact[i-1]*fact[n-i]%mod; tp=1LL*tp*quick_pow(dv)%mod;
if ((n-i)&1) ret=sub(ret,tp); else ret=sum(ret,tp);
}
return ret;
}
int main()
{
scanf("%d%d%d",&A,&n,&mod); DP(n,m=n<<1|1);
RI i; for (i=1;i<=m;++i) g[i]=f[n][i];
for (fact[0]=i=1;i<=m;++i) fact[i]=1LL*fact[i-1]*i%mod;
return printf("%d",1LL*Lagerange(m,g,A)*fact[n]%mod),0;
}