El pueblo unido jamas serà vencido!

[题解]calc 的两种解法

前言

\(\mathrm{{\color{black}w}{\color{red}{ind\_whisper}}}\) 爆切拉格朗日插值之后忽悠我做这个题.

但是我太菜了现在才稍微懂一点.

[集训队互测 2012] calc

calc加强版

解法 1

首先顺序可以忽略,最后乘上 \(n!\) 即可.

然后可以设计朴素 DP :

\(f_{i,j}\) 表示前 \(j\) 个数里已经选出 \(i\) 个的全部方案权值和.

写出 \(\mathcal{O} (nk)\) 的转移方程 :

\(f_{i,j} = j \times f_{i - 1,j - 1} + f_{i,j - 1}\)

想个办法将求这个的过程变成 \(\mathcal{O} (n^2)\) 的.

首先一直求到 \(f_{n,n}\)\(n^2\) 的,将其定义为一个函数 : \(F(x) = f_{n,x}\),那么如果其是一个多项式,就能仅仅靠连续求出的前几项进行拉格朗日插值求出结果.

现在尝试证明其为一个次数 \(\mathcal{O}(n)\) 级别的多项式.

将原 DP 状态差分 : \(g_{i,j} = f_{i,j} - f_{i,j - 1}\)

可以发现这时舍弃了 \(f_{i,j}\) 中不含有数 \(j\) 的方案,即 \(g_{i,j}\) 表示前 \(j\) 个数且包含 \(j\) 选出了 \(i\) 个数的全部方案权值总和.

然后写出 \(g\) 转移方程 :

\[\large g_{i,j} = j\sum_{k = 0}^{j - 1} g_{i - 1,k} \]

首先对 \(g_{i - 1}\) 求了前缀和,次数升高一次,再乘 \(j\) 次数再升高一次,共升高两次.

于是 \(g_{n}\)\(2n\) 次的.

于是 \(f_{n}\) 作为 \(g_{n}\) 的前缀和,是 \(2n + 1\) 次的.

暴力 DP 然后拉格朗日插值即可.

Code :

int n,k,p;
ll f[N][N << 1];
ll fac[N << 2],ifac[N << 2];
ll pre[N << 2],suf[N << 2];

ll lagrange(ll x,ll k) {
	k %= p;
	fac[0] = 1;rep(i,1,x) fac[i] = fac[i - 1] * i % p;
	ifac[x] = qpow(fac[x],p - 2,p);
	repb(i,x,1) ifac[i - 1] = ifac[i] * i % p;
	pre[0] = 1,suf[x + 1] = 1;
	rep(i,1,x) pre[i] = pre[i - 1] * (ll)(k - i) % p;
	repb(i,x,1) suf[i] = suf[i + 1]  * (ll)(k - i) % p;
	ll res = 0;
	rep(i,1,x) {
		ll tmp = f[n][i] * pre[i - 1] % p * suf[i + 1] % p * ifac[i - 1] % p * ifac[x - i] % p;
		if((x - i) & 1) tmp = p - tmp;
		res = (res + tmp) % p;
	}
	return res;
}

int main() {
	init_IO();
	k = read(),n = read(),p = read();
	rep(i,0,(n << 1 | 1)) f[0][i] = 1;
	rep(i,1,n) rep(j,1,(n << 1 | 1))
		f[i][j] = (f[i][j - 1] + f[i - 1][j - 1] * (ll)j) % p;
	ll ans = lagrange((n << 1 | 1),k);
	write(ans * fac[n] % p),enter;
	end_IO();
	return 0;
}

解法 2

依然把顺序忽略,最后乘上 \(n!\)

首先这个组合选数然后合在一起是一个每个数单个求 \(\mathbf{OGF}\) 封闭形式是很简单的,那么总的结果就是每个 \(\mathbf{OGF}\) 封闭形式卷积 :

\[\large F(x) = \prod_{i = 1}^{k} (1 + ix) \]

但是 \(\prod\) 可比 \(\sum\) 可怕太多了,考虑如何让乘法变成加法,对每个取单个数的 \(\mathbf{OGF}\)\(\ln\) 然后加起来最后 \(\mathrm{exp}\).

也就是求 : \(\ln (1 + kx)\) 化成一个级数的形式.

首先可以对 \(\ln\) 求导然后积分回来,然后得到等量关系 :

\[\large \ln(1 + kx) = \int \frac{k}{1 + kx} \mathrm{d}x \]

对于 \(\dfrac{x}{1 + kx}\) 这样的分式可以将其看作等比数列求和的结果来展开 :

\[\large \int \frac{k}{1 + kx} \mathrm{d}x = \int (k \sum_{i = 0}^{\infty} (-kx)^i) \mathrm{d}x \]

然后众所周知和式和积分都是求和,用和式的交换求和次序技巧 :

\[\large \sum_{i = 0}^{\infty}(-1)^i k^{i + 1} \int x^i \mathrm{d} x \]

发现里面的部分的积分很好求,把积分干掉换成分式 :

\[\large \sum_{i = 0}^{\infty}(-1)^i k^{i + 1} \frac{x ^{i + 1}}{i + 1} \]

然后看着 \(i + 1\) 很别扭,稍微改写一下 :

\[\large \sum_{i = 1}^{\infty}(-1)^{i - 1} k^{i} \frac{x ^{i}}{i} \]

然后这是对于一个数取数情况贡献的 \(\mathbf{OGF}\),再看一眼原式 :

\[\large F(x) = \prod_{i = 1}^{k} \color{red}{(1 + ix)} \]

其中标红部分已经可以用上面求出的无限和式来替换,然后再对 \(F(x)\)\(\ln\)\(\prod\) 变成 \(\sum\) :

\[\large \ln F(x) = \sum_{i = 1}^{k} \ln(1 + ix) \]

\[\large \ln F(x) = \sum_{i = 1}^{k} \sum_{j = 1}^{\infty} (-1)^{j - 1} i^{j} \frac{x ^{j}}{j} \]

再交换一次求和次序 :

\[\large \ln F(x) = \sum_{j = 1}^{\infty} \frac{(-1)^{j - 1}}{j} \sum_{i = 1}^{k} i^j x^j \]

所以就是求 \(\sum_{i = 1}^{k} i^j\) 比较重要.

如果只是普通版那就可以参考 这道题 使用拉格朗日插值即可计算,后面 \(\mathrm{exp}\) 的部分就交给任意模数多项式乘法/多项式乘法逆了.

现在来看加强版,模数是 \(998244353\),非常良心.

但是搜遍全网会发现自然数幂和通常说的都是这样一个玩意 :

\[\large S_k(n) = \sum_{i = 1}^{n} i^k \]

然后无论是斯特林数还是伯努利数还是多一个 \(\log\) 的分治 FFT 都是在求同一 \(k\) 不同 \(n\) 的情况,现在求的是同一 \(n\) 不同 \(k\) 的情况.

怎么说呢,就很像第二类斯特林数行/列之间的关系.

那还是直接写生成函数吧.

写出 \(\left< S_0(k),S_1(k),S_2(k),\cdots \right>\) 这样一个数列的 \(\mathbf{EGF}\).

\[\begin{aligned}\large G(x) &= \sum_{n = 0}^{\infty} \frac{\sum_{i = 1}^{k}i^n} {n!} x^n\\ &= \sum_{i = 1}^{k} \sum_{n = 0}^{\infty} \frac{i^nx^n}{n!}\\ \end{aligned}\]

然后众所周知泰勒展开有一个非常有名的例子就是 把 \(e^x\)\(x = 0\) 展开 :

\[\large e^x = \sum_{i = 0}^{\infty} \frac{x^i}{i!} \]

于是上式化为 :

\[\large \sum_{i = 1}^{k} \sum_{n = 0}^{\infty} \frac{i^nx^n}{n!} = \sum_{i = 1}^{k} e^{ix} \]

是一个等比数列,套求和公式.

\[\large G(x) = \frac{e^{kx} - 1}{1 - e^{-x}} \]

然后发现不满足零次项为 \(1\) ,但是零次项分母也是 \(0\),同时除以 \(x\),一口把那一项吞了.

成功将复杂度降低到 \(\mathcal{O} (n\log n)\)

Code :

PolyInv,PolyMul,PolyExp 参见 这里

inline void init_calc(int n) {
	fac[0] = 1;
	rep(i,1,n) fac[i] = (ll)fac[i - 1] * i % MOD;
	ifac[n] = qpow(fac[n]);
	repb(i,n,1) ifac[i - 1] = (ll)ifac[i] * i % MOD;
}

int main() {
	init_IO();
	init_root();
	int k = read(),m = read() + 2;
	init_calc(m);
	int pw = k;
	repl(i,0,m) {
		F[i] = (ll)pw * ifac[i + 1] % MOD;
		pw = (ll)pw * k % MOD;
	}
	repl(i,0,m)
		G[i] = ((i + 1) & 1) ? ifac[i + 1] : MOD - ifac[i + 1];
	PolyInv(G,H,m);
	PolyMul(F,H,F,m,m);
	mems(G,0);
	repl(i,1,m) {
		if(i & 1) G[i] = fac[i - 1];
		else G[i] = MOD - fac[i - 1];
		G[i] = ((ll)G[i] * F[i]) % MOD;
	}
	mems(F,0);
	PolyExp(G,F,m);
	repl(i,1,m - 1)
		write((ll)F[i] * fac[i] % MOD),enter;
	end_IO();
	return 0;
}
posted @ 2022-01-30 19:30  AstatineAi  阅读(87)  评论(0编辑  收藏  举报