您的位置:首页 > 其它

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

2017-08-16 09:51 218 查看

题目大意

​  一个序列a1,…,an是合法的,当且仅当:

​  长度为给定的n。

​  a1,…,an都是[1,m]中的整数。

​  a1,…,an互不相等。

​  一个序列的值定义为它里面所有数的乘积,即a1×a2×⋯×an。

  求所有不同合法序列的值的和。

​  两个序列不同当且仅当他们任意一位不一样。

​  输出答案对一个数p取余的结果。

  n≤500,m≤109,p≤109,n+1<m<p且p是质数。

题解

​  这题做法很多种。

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

​  我们先打个表:

f012
0100
1110
2134
31622
411070
5115170
6121350
​  什么?你看不出来?

f012
0100
11i0
212i−12i2−2i
313i−36i2−12i+4
414i−612i2−36i+22
515i−1020i2−80i+70
616i−1530i2−150i+170
  你还是看不出来?那我就直接告诉你吧。fi,0=1,fi,1=12i2−12i,fi,2=14i4+16i3−14i2−16i。我们会发现,fi,j是一个最高次项为2j的多项式,也就是说,fm,n是一个最高次项为2n的多项式。我们只用求出0到2n次项的系数就可以求答案了。我们可以把前面0~2n个fi,n求出来,就可以用拉格朗日插值插出多项式了。

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

​  时间复杂度:O(n2)

代码

#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\n",f[m]
);
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]
*s1%p*fp(s2,p-2)%p)%p;
}
ans=(ans%p+p)%p;
printf("%lld\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: