【动态规划】集训队互测2012 calc

【动态规划】集训队互测2012 calc

题目描述

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

  • a1,a2,,an 都是 [1,k] 中的整数。
  • a1,a2,,an 互不相等。

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

求所有不同合法序列的值的和对 p 取模后的结果。两个序列不同当且仅当他们任意一位不同。

对于 100% 的数据,k109n500p109,保证 p 为素数,保证 n+1<k<p

前置芝士:拉格朗日插值

算法描述

考虑dp,我们发现如果乱序不是很好dp,所以我们假设序列递增,最后乘上一个n!就好了。我们设dpi,j表示前i个数用[1,j]凑成的合法序列值之和,则有以下转移:

i个数是jdpi,j+=dpi1,j1j

i个数不是jdpi,j+=dpi,j1

所以

dpi,j=dpi1,j1j+dpi,j1

最后答案就是dpn,kn!,时间复杂度O(nk)

黑题切了

然而我们发现k109,并不能通过此题,那么如何加速这个式子呢?

考虑到k很大,我们试图将复杂度变得只和n有关,这个dp式子有一个性质:它只由乘法和加法组成,而且是一个没有minmax,没有多种情况的递推式。并且在n一定的情况下,dpn,j只与j有关,我们不妨大胆假设dpn,j是一个关于j的多项式,设它的次数是g(n)

首先有一结论:

设多项式f(x)的次数是n,那么它的差分f(x)f(x1)的次数是n1

所以我们可以知道,dpn,jdpn,j1的次数是g(n)1

通过递推式,我们可以得出:

dpn,jdpn,j1=jdpn1,j1

因为dp是关于j的多项式,所以乘j会使次数加1,可得dpn,jdpn,j1的次数是g(n1)+1

联立得:

g(n)1=g(n1)+1

g(n)g(n1)=2

因为g(0)=0(前0个数种类为1,是0次多项式,即常数),所以:

g(n)=2n

所以我们最终确定了dpn,j是一个2n次多项式,要求它在k处的值,而k又很大,可以使用拉格朗日插值,dp算出(1,dpn,1)(2n+1,dpn,2n+1)2n+1个连续点,然后代入插值就可以求出dpn,k的值,然后乘上n!即可。这样,时间复杂度就转化成了O(n2),可以通过本题。

前文已经阐述过,这样优化的关键条件就是dp式子的特殊性质(其实所有dp优化都是这样),本题的特殊性质就是dp是一个只和乘法和加法有关的,没有多种情况、分类讨论的简单递推式,并且目标维中有一个很大,将其设为多项式的自变量,用拉插就可以完成计算。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1005;
ll n,k,p,dp[N][N],y[N],pre[N],suf[N],inv[N];
inline ll lag(ll len,ll X)
{
	ll ret = 0; pre[0] = 1;suf[len + 1] = 1;
	for(int i = 1;i <= len;i++) pre[i] = pre[i - 1] * ((X - i) % p + p) % p;
	for(int i = len;i >= 1;i--) suf[i] = suf[i + 1] * ((X - i) % p + p) % p;
	for(int i = 1;i <= len;i++)
	{
		ll res = y[i] * pre[i - 1] % p * suf[i + 1] % p * inv[i - 1] % p * inv[len - i] % p;
		if((len - i) & 1) res = p - res;
		ret = (ret + res) % p;
	}
	return ret;
}
int main()
{
	cin>>k>>n>>p;
	inv[0] = inv[1] = 1;
	for(int i = 2;i <= N - 1;i++) inv[i] = (p - (p / i)) * inv[p % i] % p;
	for(int i = 1;i <= N - 1;i++) inv[i] = inv[i] * inv[i - 1] % p;
	memset(dp,0,sizeof(dp));
	for(int i = 0;i <= 2 * n + 1;i++) dp[0][i] = 1;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= 2 * n + 1;j++)
			dp[i][j] = dp[i - 1][j - 1] * j % p + dp[i][j - 1],dp[i][j] %= p;
	for(int i = 1;i <= 2 * n + 1;i++) y[i] = dp[n][i];
	ll ans = lag(2 * n + 1,k);
	for(int i = 1;i <= n;i++) ans = ans * i % p;
	cout<<ans;
	return 0;
 } 
posted @   The_Last_Candy  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示