【洛谷4463】[集训队互测2012] calc(动态规划+拉格朗日插值)
大致题意: 规定一个合法序列需要满足所有数都是\([1,k]\)中互不相同的整数,且一个序列的值为该序列中所有数的乘积。求所有长度为\(n\)的合法序列值之和。
动态规划
首先由互不相同这个条件,不难想到强制序列有序,然后只要给最终答案乘上\(n!\)即可。
于是就可以设\(f_{i,j}\)表示对于前\(i\)个数,所取最大值小于等于\(j\)时的答案。(这样定义就能使得最终答案为\(f_{n,k}\))
转移只要判断当前位是否选择\(j\),即可得出方程:
\[f_{i,j}=f_{i-1,j-1}\times j+f_{i,j-1}
\]
由于\(k\)的范围很大,这样肯定会\(T\)飞,因此就需要考虑一些玄学优化。
拉格朗日插值
这种优化\(DP\)的方式我真是死都想不到。。。
考虑我们假设\(f_{i,j}\)为\(j\)的\(g(i)\)次函数,然后我们对原先的转移方程移项可以得到:
\[f_{i,j}-f_{i,j-1}=f_{i-1,j-1}\times j
\]
一个众所周知的结论,对于一个\(n\)次函数\(F(x)\),\(F(x)-F(x-1)\)必然为\(n-1\)次函数。
\(F(x)\)恰好对应了此处的\(f_{i,j}\)。也就是说,如果我们分别考虑等式两边\(j\)的次数,就可以列出一个有关次数的等式:
\[g(i)-1=g(i-1)+1\Rightarrow g(i)=g(i-1)+2
\]
又由于\(g(0)=0\),因此,最终我们发现\(g(i)=2i\),则\(g(n)=2n\)。
我们最后要求的只是\(f_{n,k}\),也就是一个\(2n\)次函数,那么我们只需要求出\(2n+1\)个点值,然后利用拉格朗日插值就可以求出\(f_{n,k}\)了。
而\(2n+1\)个点值只需要\(DP\)出\(f_{n,1\sim 2n+1}\),那么每对\((i,f_{n,i})\)自然都是一个我们需要的点值。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500
using namespace std;
int n,k,X,f[N+5][2*N+5],Fac[2*N+5];
I int QI(RI x,RI y=X-2) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
I int Lagrange(CI n,CI k,int *y)//拉格朗日插值(这里写的是x取值连续时的插值法,其实朴素的O(n^2)也可以)
{
if(k<=n) return y[k];RI i,j,p=1,s=0;for(i=1;i<=n;++i) p=1LL*p*(k-i)%X;
for(i=1;i<=n;++i) s=(1LL*y[i]*QI(1LL*((n-i)&1?-1:1)*(k-i)%X*Fac[i-1]%X*Fac[n-i]%X)+s)%X;
return 1LL*p*s%X;
}
int main()
{
RI i,j;for(scanf("%d%d%d",&k,&n,&X),i=0;i<=2*n+1;++i) f[0][i]=1;//初始化
for(i=1;i<=n;++i) for(j=1;j<=2*n+1;++j) f[i][j]=(1LL*f[i-1][j-1]*j+f[i][j-1])%X;//DP
for(Fac[0]=i=1;i<=2*n+1;++i) Fac[i]=1LL*Fac[i-1]*i%X;//初始化阶乘
return printf("%d",1LL*Lagrange(2*n+1,k,f[n])*Fac[n]%X),0;//插值求出答案,记得乘上n的阶乘
}
待到再迷茫时回头望,所有脚印会发出光芒