Luogu P4463 [国家集训队] calc

题目大意

%   给定 [ 1 , A ] [1,A] [1,A] 之间的数,从其中取出 m m m 个数排列,每种方案的价值为序列中所有元素的乘积,求所有方案的价值和在 m o d mod mod 意义下的值。
   数据范围  1 ⩽ A ⩽ 1 0 9 , n ⩽ 500 , m o d ∈ prime , m o d ⩽ 1 0 9 , m o d > A > n + 1 1\leqslant A\leqslant 10^9,n\leqslant 500,mod\in \text{prime},mod\leqslant 10^9,mod>A>n+1 1A109,n500,modprime,mod109,mod>A>n+1

题解

%   先考虑动态规划,直接问什么定义什么似乎很难转移。那就只能分析题目了。首先,我们发现方案的价值和顺序无关,因而我们可以求出严格上升序列的价值,然后乘上排列方案数 n ! n! n!
  定义:用 1 ∼ j 1\sim j 1j 填满序列的前 i i i 个位置,能得到的严格上升序列的总价值为 f [ i ] [ j ] f[i][j] f[i][j]
  那么转移就很好写了: f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] × j + f [ i ] [ j − 1 ] f[i][j]=f[i-1][j-1]\times j+f[i][j-1] f[i][j]=f[i1][j1]×j+f[i][j1]  则答案为 f [ n ] [ A ] × n ! f[n][A]\times n! f[n][A]×n!,时间复杂度为 Θ ( n × A ) \Theta(n\times A) Θ(n×A),对于本题来说显然是错的。
  考虑优化,如果我们知道 f [ n ] [ A ] f[n][A] f[n][A] 的次数,那就可以套拉格朗日插值了。
  尝试令 f [ i ] [ j ] f[i][j] f[i][j] 是一个关于 i i i g ( n ) g(n) g(n) 次多项式。我们知道,一个 i i i 次多项式差分后为一个 i − 1 i-1 i1 次多项式,因而有: f [ i ] [ j ] − f [ i − 1 ] [ j ] f[i][j]-f[i-1][j] f[i][j]f[i1][j] 为一个 g ( i ) − 1 g(i)-1 g(i)1 次多项式。对方程式移项,得 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 ) − 1 g(i)-1 g(i)1 次多项式,右侧为 g ( n − 1 ) + 1 g(n-1)+1 g(n1)+1 次多项式,因而有:
g ( n ) − 1 = g ( n − 1 ) + 1 g(n)-1=g(n-1)+1 g(n)1=g(n1)+1  化简,得: g ( n ) = g ( n − 1 ) + 2 g(n)=g(n-1)+2 g(n)=g(n1)+2  可以用断言并用生成函数证明: g ( n ) = 2 n g(n)=2n g(n)=2n  因而原方程为一个 2 n 2n 2n 次的多项式。我们先计算出 f ( n , 1 ) ∼ f [ n ] [ 2 n + 1 ] f(n,1)\sim f[n][2n+1] f(n,1)f[n][2n+1],时间复杂度为 Θ ( n ) \Theta(n) Θ(n)。然后用拉格朗日插值求出 f [ n ] [ A ] f[n][A] f[n][A],总时间复杂度为 T ( n ) = Θ ( n 2 ) T(n)=\Theta(n^2) T(n)=Θ(n2)

代码

#include<bits/stdc++.h>
using namespace std;
#define maxn 510
long long A,n;
long long mod;
long long f[maxn][maxn*2];
long long x[maxn],y[maxn];
long long pow_t(long long a,long long b){
	if(b==1) return a%mod;
	long long t=pow_t(a,b>>1);
	if((b&1)==0) return t*t%mod;
	return t*t%mod*a%mod;
}
#define inv(x) pow_t(x,mod-2)
long long ft(long long n,long long k){
	long long ans=0;
	for(int i=0;i<n;i++){
		long long up=y[i]%mod,dn=1;
		for(int j=0;j<n;j++)
			if(i!=j)
				up=up*(k-x[j]+mod)%mod,dn=dn*(x[i]-x[j]+mod)%mod;
		ans=(ans+up*inv(dn)%mod)%mod;
	} return ans;
}
int main(void)
{
	scanf("%lld%lld%lld",&A,&n,&mod);
	long long fac_n=1;
	for(int i=1;i<=n;i++)
		fac_n=fac_n*i%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]=(f[i-1][j-1]*j%mod+f[i][j-1])%mod;
	for(int i=1;i<=2*n+1;i++)
		x[i-1]=i,y[i-1]=f[n][i];
	printf("%lld",fac_n*ft(2*n+1,A)%mod);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值