【BZOJ2655】calc DP 数学 拉格朗日插值

题目大意

​  一个序列(a_1,ldots,a_n)是合法的,当且仅当:
​  长度为给定的(n)
​  (a_1,ldots,a_n)都是([1,m])中的整数。
​  (a_1,ldots,a_n)互不相等。
​  一个序列的值定义为它里面所有数的乘积,即(a_1 imes a_2 imescdots imes a_n)
  求所有不同合法序列的值的和。
​  两个序列不同当且仅当他们任意一位不一样。
​  输出答案对一个数(p)取余的结果。

  (nleq500,mleq {10}^9,pleq{10}^9,n+1<m<p)(p)是质数。

题解

​  这题做法很多种。

​  设(f_{i,j})为前(i)个数中选(j)个数的所有方案的值的和,容易得到递推式:(f_{0,0}=1,f_{i,j}=f_{i-1,j-1} imes i imes j+f_{i-1,j})。最后(ans=f_{m,n})。但是这题(m)很大,不能直接求出答案。怎么办呢?

​  我们先打个表:

(f) (0) (1) (2)
(0) (1) (0) (0)
(1) (1) (1) (0)
(2) (1) (3) (4)
(3) (1) (6) (22)
(4) (1) (10) (70)
(5) (1) (15) (170)
(6) (1) (21) (350)

​  什么?你看不出来?

(f) (0) (1) (2)
(0) (1) (0) (0)
(1) (1) (i) (0)
(2) (1) (2i-1) (2i^2-2i)
(3) (1) (3i-3) (6i^2-12i+4)
(4) (1) (4i-6) (12i^2-36i+22)
(5) (1) (5i-10) (20i^2-80i+70)
(6) (1) (6i-15) (30i^2-150i+170)

  你还是看不出来?那我就直接告诉你吧。(f_{i,0}=1,f_{i,1}=frac12i^2-frac12i,f_{i,2}=frac14i^4+frac16i^3-frac14i^2-frac16i)。我们会发现,(f_{i,j})是一个最高次项为(2j)的多项式,也就是说,(f_{m,n})是一个最高次项为(2n)的多项式。我们只用求出(0)(2n)次项的系数就可以求答案了。我们可以把前面(0)~(2n)(f_{i,n})求出来,就可以用拉格朗日插值插出多项式了。

​  这道题因为是求某一个点的值,并不要求求出多项式,而且(x)取的是([0,2n]),所以可以(O(n))求出答案。然而并没有什么用,因为前面的DP已经是(O(n^2))的了。

​  时间复杂度:(O(n^2))

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
ll p;
ll f[1010][1010];
ll fp(ll a,ll b)
{
	ll s=1;
	while(b)
	{
		if(b&1)
			s=s*a%p;
		a=a*a%p;
		b>>=1;
	}
	return s;
}
int main()
{
	int n,m;
	scanf("%d%d%lld",&m,&n,&p);
	int i,j;
	memset(f,0,sizeof f);
	f[0][0]=1;
	for(i=1;i<=2*n;i++)
	{
		f[i][0]=f[i-1][0];
		for(j=1;j<=n;j++)
			f[i][j]=(f[i-1][j-1]*i%p*j+f[i-1][j])%p;
	}
	if(m<=2*n)
	{
		printf("%lld
",f[m][n]);
		return 0;
	}
	ll ans=0;
	for(i=0;i<=2*n;i++)
	{
		ll s1=1,s2=1;
		for(j=0;j<=2*n;j++)
			if(j!=i)
			{
				s1=(s1*(m-j))%p;
				s2=(s2*(i-j))%p;
			}
		ans=(ans+f[i][n]*s1%p*fp(s2,p-2)%p)%p;
	}
	ans=(ans%p+p)%p;
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/ywwyww/p/8511148.html