P4463 [集訓隊互測2012] calc(dp + 拉格朗日插值優化)

在這裏插入圖片描述


考慮 dp 求解,dp 構造升序序列,最後乘上 n!n!

f[i][j]f[i][j] 表示前 ii 個位置,最大值小於等於 jj 的貢獻,轉移方程:f[i][j]=f[i1][j1]j+f[i][j1]f[i][j] = f[i - 1][j - 1] * j + f[i][j - 1]

最終答案是 f[n][k]f[n][k] ,k 非常大肯定無法求解,考慮優化:
g[i][j]g[i][j] 表示前 ii 個位置,第 ii 個放 jj 的貢獻,轉移方程:g[i][j]=jk=1j1g[i1][k]\displaystyle g[i][j] = j*\sum_{k = 1}^{j - 1}g[i - 1][k]
顯然有 f[n][k]=i=1kg[n][k]\displaystyle f[n][k] = \sum_{i = 1}^kg[n][k]

如果可以證明 g[n][k]g[n][k] 是一個以 kk 爲自變量的多項式,就可以使用拉格朗日插值快速求解

使用歸納法證明:
當 n = 0 時,g[0][k] = 0,結論成立
設 n > 0 且,g[n][k] 是一個以 k 爲自變量的多項式
根據轉移方程,有:g[n+1][k]=ki=1k1g[n][i]\displaystyle g[n + 1][k] =k*\sum_{i = 1}^{k - 1}g[n][i],根據k次冪和的推論,可以得知 g[n+1][k]g[n + 1][k] 是以 kk 爲自變量的多項式,當 n=0n = 0dp[n][k]dp[n][k] 是一個 00 次多項式,nn 每增一,根據轉移方程可以得出 多項式的次數 +2+ 2,因此 dp[n][k]dp[n][k] 是一個以 kk 爲自變量的 2n2n 次多項式。

因此 f[n][k]f[n][k] 是一個以 k 爲自變量的 2n+12n + 1 次多項式,求出 2n+22n + 2 個點,通過插值快速求出最終答案。不要忘了乘上 n!n!


代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e3 + 10;
typedef long long ll;
int mod,mx,n,k;
ll fac[maxn],ifac[maxn];
inline ll add(ll x, ll y) {
  	x += y;
  	if (x >= mod) x -= mod;
  	return x;
}

inline ll sub(ll x, ll y) {
	x -= y;
	if (x < 0) x += mod;
	return x;
}

inline ll mul(ll x, ll y) {
  	return x * y % mod;
}
ll fpow(ll a,ll b) {
	ll r = 1;
	while(b) {
		if (b & 1) r = mul(r,a);
		b >>= 1;
		a = mul(a,a);
	}
	return r;
}
ll cal(ll g[maxn],ll x) {			//拉格朗日插值計算多項式
	if (x <= mx) return g[x];
	ll tmp = 1,inv,ans = 0;
	for (int i = 1; i <= mx; i++)
		tmp = mul(tmp,x - i);
	for (int i = 1; i <= mx; i++) {
		ll res = 1, inv = fpow(x - i,mod - 2);
		res = mul(res,g[i]);
		res = mul(res,ifac[i - 1]);
		res = mul(res,ifac[mx - i]);
		res = mul(res,inv);
		res = mul(res,tmp);
		if ((mx - i) & 1) res = mul(res,-1);
		if (res < 0) res += mod;
		ans = add(ans,res);
	}
	return ans;
}
ll f[maxn],tp[maxn],dp[2000][2000];
int main() {
	scanf("%d%d%d",&k,&n,&mod);
	fac[0] = 1;
	for (int i = 1; i <= 4000; i++)
		fac[i] = mul(fac[i - 1],i);
	ifac[4000] = fpow(fac[4000],mod - 2);
	for (int i = 4000 - 1; i >= 0; i--)
		ifac[i] = mul(ifac[i + 1],i + 1);
	mx = 2 * n + 4;
	for (int j = 0; j <= mx; j++)
		dp[0][j] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= mx; j++)
			dp[i][j] = (1ll * dp[i - 1][j - 1] * j + dp[i][j - 1]) % mod;
	printf("%lld\n",1ll * cal(dp[n],k) * fac[n] % mod);
	return 0;
}
發佈了326 篇原創文章 · 獲贊 10 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章