bzoj 2566 calc 拉格朗日插值
calc
Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 377 Solved: 226
[Submit][Status][Discuss]
Description
一个序列a1,...,an是合法的,当且仅当:
长度为给定的n。
a1,...,an都是[1,A]中的整数。
a1,...,an互不相等。
一个序列的值定义为它里面所有数的乘积,即a1a2...an。
求所有不同合法序列的值的和。
两个序列不同当且仅当他们任意一位不一样。
输出答案对一个数mod取余的结果。
Input
一行3个数,A,n,mod。意义为上面所说的。
Output
一行结果。
Sample Input
9 7 10007
Sample Output
3611
HINT
数据规模和约定
0:A<=10,n<=10。
1..3:A<=1000,n<=20.
4..9:A<=10^9,n<=20
10..19:A<=10^9,n<=500。
全部:mod<=10^9,并且mod为素数,mod>A>n+1
Source
不得不说dp设的也是十分好的,估计自己还想不出。
f[i][j]表示,前i个元素中,选择了j个的方案数,这个转移是怎么样的呢?
f[i][j]=f[i-1][j-1]*i*j+f[i-1][j],这个转移中的第二个十分显然,第一个是什么意思,就是选择了i这个元素,
插入到j中的任意一个位置,就是j个位置离随便哪个位置都可以,然后根据乘法的分配律,结合一下,就可以了。
当然j可以大于i,就因为i可以插到后面的位置。
就算想出了这一步,下面发现这个表是一个几次的多项式我基本上不可能会发现
某大佬打了这个表,然后这个多项式怎么搞出来的真的有点厉害
但是这个多项式是没用的,因为这个多项式的系数是变化的,所以没有什么用
有没有一个多项式的系数是不变的呢?
然后就有大佬发现了
发现了这个,即f[i][j]的系数只和j有关,是一个最高项系数是2*j的多项式,然后就稳了,
这样只需要求出2*n+1个点就可以插值了,朗格朗日插值求一下m这个位置的值即可。
1 #include<cstring> 2 #include<cstdio> 3 #include<cmath> 4 #include<iostream> 5 #include<algorithm> 6 7 #define N 1007 8 #define ll long long 9 using namespace std; 10 inline int read() 11 { 12 int x=0,f=1;char ch=getchar(); 13 while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} 14 while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} 15 return x*f; 16 } 17 18 int n,m; 19 ll p,ans; 20 ll f[N][N]; 21 22 ll fast_pow(ll a,ll b) 23 { 24 ll ans=1; 25 while(b) 26 { 27 if (b&1) (ans*=a)%=p; 28 (a*=a)%=p; 29 b>>=1; 30 } 31 return ans; 32 } 33 void Lagrange() 34 { 35 for (int i=0;i<=2*n;i++) 36 { 37 ll s1=1,s2=1; 38 for (int j=0;j<=2*n;j++) 39 if (j!=i) 40 { 41 (s1*=(m-j))%=p; 42 (s2*=(i-j))%=p; 43 } 44 (ans+=f[i][n]*s1%p*fast_pow(s2,p-2)%p)%=p; 45 } 46 } 47 int main() 48 { 49 m=read(),n=read(),p=read(); 50 f[0][0]=1; 51 for (int i=1;i<=2*n;i++) 52 { 53 f[i][0]=f[i-1][0]; 54 for (int j=1;j<=n;j++) 55 f[i][j]=(f[i-1][j-1]*i%p*j+f[i-1][j])%p; 56 } 57 if (m<=2*n) 58 { 59 printf("%lld\n",f[m][n]); 60 return 0; 61 } 62 63 Lagrange(); 64 ans=(ans%p+p)%p; 65 66 printf("%lld\n",ans); 67 }