集训队互测 2012 calc 题解

题目传送门

题目大意: 一个合法的数列满足:长度为 n n n;每个数的值在 [ 1 , k ] [1,k] [1,k] 内;任意两个数不同。一个数列的值为所有数的乘积,求所有合法数列的值的和。

题解

考虑只统计上升的数列,最后答案乘 n ! n! n! 即可。

f ( i , j ) f(i,j) f(i,j) 表示前 i i i 个数的值在 [ 1 , j ] [1,j] [1,j] 内的所有方案的贡献和,考虑第 i i i 个数选不选 j j j,可以得到dp方程: f ( i , j ) = f ( i , j − 1 ) + f ( i − 1 , j − 1 ) × j f(i,j)=f(i,j-1)+f(i-1,j-1)\times j f(i,j)=f(i,j1)+f(i1,j1)×j

根据在很多地方都能看到的一个东西:若 f ( x ) f(x) f(x) 是关于 x x x n n n 次多项式,那么设 g ( x ) = f ( x + 1 ) − f ( x ) g(x)=f(x+1)-f(x) g(x)=f(x+1)f(x) g ( x ) g(x) g(x) 就是一个关于 x x x n − 1 n-1 n1 次多项式,证明显然。

f ( i , j ) f(i,j) f(i,j) 是一个关于 j j j g ( i ) g(i) g(i) 次多项式,注意到上面也有一个类似差分的东西:
f ( i , j ) − f ( i , j − 1 ) = f ( i − 1 , j − 1 ) × j f(i,j)-f(i,j-1)=f(i-1,j-1)\times j f(i,j)f(i,j1)=f(i1,j1)×j

一个 g ( i ) g(i) g(i) 次的数列,差分后得到一个 g ( i − 1 ) + 1 g(i-1)+1 g(i1)+1 次的数列,即 g ( i ) − 1 = g ( i − 1 ) + 1 g(i)-1=g(i-1)+1 g(i)1=g(i1)+1,等价于 g ( i ) = g ( i − 1 ) + 2 = 2 i + g ( 0 ) = 2 i g(i)=g(i-1)+2=2i+g(0)=2i g(i)=g(i1)+2=2i+g(0)=2i

所以 f ( i , j ) f(i,j) f(i,j) 是个 2 i 2i 2i 次的多项式,但你可能会问,为什么他就一定是个多项式呢?

这个也可以用归纳法简单证明,还是用上面的那个差分式子,由于 f ( 0 , j ) = 1 f(0,j)=1 f(0,j)=1,是个多项式,再根据 f ( i , j ) f(i,j) f(i,j) 的差分可以得到 f ( i − 1 , j − 1 ) × j f(i-1,j-1)\times j f(i1,j1)×j,而 f ( i − 1 , j − 1 ) f(i-1,j-1) f(i1,j1) 是个多项式,所以 f ( i , j ) f(i,j) f(i,j) 也是个多项式。

考虑拉格朗日插值,如果能算出 f ( n , [ 1 , 2 n + 1 ] ) f(n,[1,2n+1]) f(n,[1,2n+1]),那么就能求出 f ( n , k ) f(n,k) f(n,k) 了。而 f ( n , [ 1 , 2 n + 1 ] ) f(n,[1,2n+1]) f(n,[1,2n+1]) 可以直接 n 2 n^2 n2 dp得到,然后套一个拉格朗日插值板子就做完了。当然由于这里选取的点的 x x x 坐标连续,你也可以用广为人知的技巧优化到 O ( n ) O(n) O(n),实际上由于 n 2 n^2 n2 做法里面涉及大量取模操作,如果优化成 O ( n ) O(n) O(n) 的,虽然理论时间复杂度依然是 O ( n 2 ) O(n^2) O(n2)(来自上面的dp),但实际会快超级多。

然而我还是写了n^2的代码如下:

扫描二维码关注公众号,回复: 12425792 查看本文章
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 1010

int n,k,mod;
int f[maxn][maxn];
int ksm(int x,int y){
    
    int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
long long ans=0;

int main()
{
    
    
	scanf("%d %d %d",&k,&n,&mod);
	for(int i=0;i<=2*n+1;i++)f[0][i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=2*n+1;j++)
			f[i][j]=(1ll*f[i-1][j-1]*j+f[i][j-1])%mod;
	if(k<=2*n+1)ans=f[n][k];
	else{
    
    
		for(int i=1;i<=2*n+1;i++){
    
    
			long long tot=f[n][i];
			for(int j=1;j<=2*n+1;j++)if(i!=j)
				tot=tot*(k-j)%mod*ksm((mod+i-j)%mod,mod-2)%mod;
			ans+=tot;
		}
		ans%=mod;
	}
	for(int i=2;i<=n;i++)ans=ans*i%mod;
	printf("%lld",ans);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/112590220