【洛谷4463】[集训队互测2012] calc(动态规划+拉格朗日插值)

点此看题面

大致题意: 规定一个合法序列需要满足所有数都是([1,k])中互不相同的整数,且一个序列的值为该序列中所有数的乘积。求所有长度为(n)的合法序列值之和。

动态规划

首先由互不相同这个条件,不难想到强制序列有序,然后只要给最终答案乘上(n!)即可。

于是就可以设(f_{i,j})表示对于前(i)个数,所取最大值小于等于(j)时的答案。(这样定义就能使得最终答案为(f_{n,k})

转移只要判断当前位是否选择(j),即可得出方程:

[f_{i,j}=f_{i-1,j-1} imes 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} imes j ]

一个众所周知的结论,对于一个(n)次函数(F(x))(F(x)-F(x-1))必然为(n-1)次函数。

(F(x))恰好对应了此处的(f_{i,j})。也就是说,如果我们分别考虑等式两边(j)的次数,就可以列出一个有关次数的等式:

[g(i)-1=g(i-1)+1Rightarrow 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,1sim 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的阶乘
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4463.html