[BZFZ友谊赛]火山喷发

这篇博客讨论了一道数学竞赛题,涉及随从生命值和连续攻击次数的期望计算。作者提出了两种策略,分别是使用二元生成函数和概率分治。第一种策略通过指数型生成函数的导数优化复杂度,第二种策略利用概率的性质进行分治计算。文章详细阐述了每种方法的思路,并给出了相应的代码实现。最后,虽然两种方法的复杂度仍较高,但作者指出可能可以通过更巧妙的优化来提高效率。
摘要由CSDN通过智能技术生成

题目

传送门 to CF:这是 C F _ G Y M 102798 E \rm CF\_GYM102798E CF_GYM102798E 一道原题。

题目描述
n n n 个随从,第 i i i 个的生命值为 a i a_i ai 。现在你会连续攻击 m m m 次,每次等概率地选择一个仍然存活的随从,并对它造成 1 1 1 的伤害(使其生命值减小 1 1 1)。当一个随从的生命值降为 0 0 0 时,它立刻死亡(不再存活)。

现在问你,死亡随从数量的期望是多少。对 998244353 998244353 998244353 取模。

数据范围与提示
n ≤ 15 ,    m ≤ 200 ,    a i ≤ 200 n\le 15,\;m\le 200,\;a_i\le 200 n15,m200,ai200,保证 m ≤ ∑ i a i m\le\sum_{i}a_i miai

思路壹

我考场上就想的这种做法。但是因为很难打,并且复杂度不太对,就放弃了……

出师不利

经典题目 猎人杀,大家肯定都做过。还是用这种方法:认为死掉的随从仍然可以被攻击,只是它无效而已。

不难发现,这里有两个要素:有效攻击次数;总攻击次数。前者是满足题目条件用的,后者是计算概率用的。

那么我们可以很轻松地写出一个二元生成函数,用 x x x 代表有效攻击, y y y 代表总攻击:
F i ( x , y ) = ∑ b ≥ 0 x min ⁡ ( a i , b ) ⋅ y b b ! ⋅ ( 1 n ) b F_i(x,y)=\sum_{b\ge 0}x^{\min(a_i,b)}\cdot \frac{y^b}{b!}\cdot\left({1\over n}\right)^b Fi(x,y)=b0xmin(ai,b)b!yb(n1)b

显然 y y y 应当是指数型,因为 y y y 的合并是需要用组合数的。它计算的是排列。

可以稍微化简一下,得到
F i ( x , y ) = x a i ( e y / n − ∑ j < a i y j n j ⋅ j ! ) + ∑ j < a i x j y j n j ⋅ j ! F_i(x,y)=x^{a_i}\left(e^{y/n}-\sum_{j<a_i}\frac{y^j}{n^j\cdot j!}\right)+\sum_{j<a_i}\frac{x^jy^j}{n^j\cdot j!} Fi(x,y)=xai(ey/nj<ainjj!yj)+j<ainjj!xjyj

二元生成函数的卷积,大家都会做,就是 f a M + b f_{aM+b} faM+b x a y b x^{a}y^b xayb 的系数,然后卷积,其中 ⌊ M − 1 2 ⌋ \lfloor\frac{M-1}{2}\rfloor 2M1 y y y 的次数最大值。考虑到 x x x 的范围是 m m m y y y 的范围是 ∑ a i ≤ n a \sum a_i\le na aina,复杂度是
O ( n m a log ⁡ n m a ) \mathcal O(nma\log nma) O(nmalognma)

那么,我们只需要尽力求出
P ( x , y ) = ∏ i F i ( x , y ) = ∑ j = 0 n A j ( x , y ) ⋅ e y j / n P(x,y)=\prod_i F_i(x,y)=\sum_{j=0}^{n}A_{j}(x,y)\cdot e^{yj/n} P(x,y)=iFi(x,y)=j=0nAj(x,y)eyj/n

然后我们求 ∑ j = 0 + ∞ [ x m y j ]    j ! ⋅ P ( x , y ) \sum_{j=0}^{+\infty}[x^my^j]\;j!\cdot P(x,y) j=0+[xmyj]j!P(x,y) 就毫不费力了。毕竟 e y j / n = ∑ t = 0 + ∞ ( y j ) t n t ⋅ t ! e^{yj/n}=\sum_{t=0}^{+\infty}\frac{(yj)^t}{n^t\cdot t!} eyj/n=t=0+ntt!(yj)t 嘛。枚举 A j ( x , y ) A_j(x,y) Aj(x,y) y y y 的次数 k k k,乘 e y j / n e^{yj/n} eyj/n ∑ t = 0 + ∞ j t y t + k n t ⋅ t ! \sum_{t=0}^{+\infty}\frac{j^t y^{t+k}}{n^t\cdot t!} t=0+ntt!jtyt+k,第 j j j 项乘 j ! j! j! 求和得到
∑ t = 0 + ∞ j t ⋅ ( t + k ) ! n t ⋅ t ! \sum_{t=0}^{+\infty}\frac{j^t\cdot(t+k)!}{n^t\cdot t!} t=0+ntt!jt(t+k)!

我们有理由相信,这是可以 O ( log ⁡ k ) \mathcal O(\log k) O(logk) 计算的。那么统计答案的总复杂度就是 O ( n 2 a log ⁡ n a ) \mathcal O(n^2a\log na) O(n2alogna) 了。

怎么求 A j ( x , y ) A_j(x,y) Aj(x,y) 呢?考虑直接 N T T \tt NTT NTT 的话,要计算 n 2 n^2 n2 组二元卷积,所以复杂度是
O ( n 3 m a log ⁡ n m a ) \mathcal O(n^3ma\log nma) O(n3malognma)

这是概率,现在要求期望,只需要钦定某个随从必然被干掉。即只取 b ≥ a i b\ge a_i bai 的情况。用算出的 P ( x , y ) P(x,y) P(x,y) 减去后面的 ∑ j < a i x j y j n j ⋅ j ! \sum_{j<a_i}{x^jy^j\over n^j\cdot j!} j<ainjj!xjyj 就行了。这个就预处理前缀、后缀,然后三者相乘。

容易发现,复杂度已经受不了了……

工业革命

原因在于 n , m n,m n,m 并不对等,二者数据范围相距悬殊。这么小的 n n n,肯定引导我们往 2 n ∼ 3 n 2^n\sim 3^n 2n3n 左右去思考。

考虑枚举 b ≥ a i b\ge a_i bai 的集合 S S S,因为此时 ∑ j < a i x j y j n j ⋅ j ! \sum_{j<a_i}\frac{x^jy^j}{n^j\cdot j!} j<ainjj!xjyj 满足 x , y x,y x,y 次数相同,就不需要使用二元生成函数了。一元生成函数的卷积,复杂度就可以明显优化下来。

具体而言,我们只需要求
[ y m − ∑ j ∈ S a j ] ∏ j ∉ S ( ∑ b < a j y b n b ⋅ b ! ) [y^{m-\sum_{j\in S}a_j}]\prod_{j\notin S}\left(\sum_{b<a_j}\frac{y^b}{n^b\cdot b!}\right) [ymjSaj]j/Sb<ajnbb!yb

这是被 x x x 的次数限制住了的。然后再求
∏ j ∈ S ( e y / n − ∑ b < a j y b n b ⋅ b ! ) \prod_{j\in S}\left(e^{y/n}-\sum_{b<a_j}\frac{y^b}{n^b\cdot b!}\right) jSey/nb<ajnbb!yb

注意这里 y y y 的次数,有一个很强劲的限制:它是钦定了被干掉的随从,所以 ∑ a ≤ m \sum a\le m am,所以 y y y 的最高次数是 m m m 而不是 n a na na

但是我们有一个很高妙的做法:要知道,系数为 1 1 1 的指数型生成函数,求导 是不会大变样的。我们试试,对 y y y 求导:
( e y / n − ∑ b < a j y b n b ⋅ b ! ) ′ = e y / n n − ∑ b = 1 a j − 1 y b − 1 n b ⋅ ( b − 1 ) ! \left(e^{y/n}-\sum_{b<a_j}\frac{y^b}{n^b\cdot b!}\right)'=\frac{e^{y/n}}{n}-\sum_{b=1}^{a_j-1}\frac{y^{b-1}}{n^b\cdot(b-1)!} ey/nb<ajnbb!yb=ney/nb=1aj1nb(b1)!yb1

记左式(原式)为 F j ( y ) F_j(y) Fj(y),右式则为 F j ′ ( y ) F_j'(y) Fj(y),不难发现
F j ( y ) = n ⋅ F j ′ ( y ) − y a j − 1 n a j − 1 ⋅ ( a j − 1 ) ! ⇒ F j ′ ( y ) = F j ( y ) n + y a j − 1 n a j ⋅ ( a j − 1 ) ! F_j(y)=n\cdot F_j'(y)-\frac{y^{a_j-1}}{n^{a_j-1}\cdot(a_j-1)!}\\ \Rightarrow F'_j(y)=\frac{F_j(y)}{n}+\frac{y^{a_j-1}}{n^{a_j}\cdot(a_j-1)!} Fj(y)=nFj(y)naj1(aj1)!yaj1Fj(y)=nFj(y)+naj(aj1)!yaj1

那么,我们将目标式子 ∏ j ∈ S F j ( y ) \prod_{j\in S}F_j(y) jSFj(y) 求导试一试:
[ ∏ j ∈ S F j ( y ) ] ′ = ∑ x ∈ S F x ′ ( y ) ∏ j ∈ S j ≠ x F j ( y ) = ∑ x ∈ S [ F x ( y ) n + y a x − 1 n a x ⋅ ( a x − 1 ) ! ] ∏ j ∈ S j ≠ x F j ( y ) = ∏ j ∈ S F j ( y ) + ∑ x ∈ S y a x − 1 n a x ⋅ ( a x − 1 ) ! ∏ j ∈ S j ≠ x F j ( y ) \begin{aligned} &\left[\prod_{j\in S}F_j(y)\right]' =\sum_{x\in S}F_x'(y)\prod_{j\in S}^{j\ne x}F_j(y)\\ &=\sum_{x\in S}\left[\frac{F_x(y)}{n}+\frac{y^{a_x-1}}{n^{a_x}\cdot(a_x-1)!}\right]\prod_{j\in S}^{j\ne x}F_j(y)\\ &=\prod_{j\in S}F_j(y)+\sum_{x\in S}\frac{y^{a_x-1}}{n^{a_x}\cdot(a_x-1)!}\prod_{j\in S}^{j\ne x}F_j(y) \end{aligned} jSFj(y)=xSFx(y)jSj=xFj(y)=xS[nFx(y)+nax(ax1)!yax1]jSj=xFj(y)=jSFj(y)+xSnax(ax1)!yax1jSj=xFj(y)

怎么从导数反推回原式呢?考虑到
[ A j ( y ) ⋅ e y j / n ] ′ = A j ( y ) ⋅ j ⋅ e y j / n n + A j ′ ( y ) ⋅ e y j / n \left[A_j(y)\cdot e^{yj/n}\right]'=A_j(y)\cdot \frac{j\cdot e^{yj/n}}{n}+A_j'(y)\cdot e^{yj/n} [Aj(y)eyj/n]=Aj(y)njeyj/n+Aj(y)eyj/n

那么,当我们知道左式,又知道 A j ( y ) A_j(y) Aj(y) k k k 次项时,我们就可以推出 A j ′ ( y ) A'_j(y) Aj(y) k k k 次项,进而知道 A j ( y ) A_j(y) Aj(y) k + 1 k+1 k+1 次项。

对应到这个式子上,我们对于每个 e y j / n e^{yj/n} eyj/n 单独求系数。此时,我们考虑去计算右式的 k k k 次项,这当然是 O ( ∣ S ∣ ) \mathcal O(|S|) O(S) 的,然后就求出了 k + 1 k+1 k+1 次项。所以总复杂度
O ( n 2 m ⋅ 2 n ) \mathcal O(n^2m\cdot 2^n) O(n2m2n)

新能源

这是另一个求解 ∏ ( e y / n − ∑ y b n b ⋅ b ! ) \prod(e^{y/n}-\sum\frac{y^b}{n^b\cdot b!}) (ey/nnbb!yb) 的方法,由 T i w - A i r - O A O \sf Tiw\text-Air\text-OAO Tiw-Air-OAO 提供。
它同时也解决了第一个式子 ∏ ∑ y b n b ⋅ b ! \prod\sum\frac{y^b}{n^b\cdot b!} nbb!yb,可能更高妙。

考虑再枚举一次 e y / n − ∑ b < a j y b n b ⋅ b ! e^{y/n}-\sum_{b<a_j}\frac{y^b}{n^b\cdot b!} ey/nb<ajnbb!yb 中,究竟是二者的哪一个,就可以直接得知 e e e 的指数了。那么我们得到的就是
∑ T ⫅ S ( − 1 ) ∣ T ∣ e y ( ∣ S ∣ − ∣ T ∣ ) / n ∏ j ∈ T ( ∑ b < a j y b n b ⋅ b ! ) \sum_{T\subseteqq S}(-1)^{|T|}e^{y(|S|-|T|)/n}\prod_{j\in T}\left(\sum_{b<a_j}\frac{y^b}{n^b\cdot b!}\right) TS(1)Tey(ST)/njTb<ajnbb!yb

你以为它退化成了 3 n 3^n 3n 。注意到 ∣ T ∣ |T| T 相等时, e e e 的次数是固定的,考虑合并它们。定义
Z S , k ( y ) = ∑ T ⫅ S ∣ T ∣ = k ∏ j ∈ T ( ∑ b < a j y b n b ⋅ b ! ) Z_{S,k}(y)=\sum_{T\subseteqq S}^{|T|=k}\prod_{j\in T}\left(\sum_{b<a_j}\frac{y^b}{n^b\cdot b!}\right) ZS,k(y)=TST=kjTb<ajnbb!yb

还用上面的法子,求导,不难发现
F T ′ ( y ) = n F T ( y ) − ∑ x ∈ T y a x − 1 n a x − 1 ⋅ ( a x − 1 ) ! F T − { x } F_T'(y)=nF_T(y)-\sum_{x\in T}\frac{y^{a_x-1}}{n^{a_x-1}\cdot(a_x-1)!}F_{T-\{x\}} FT(y)=nFT(y)xTnax1(ax1)!yax1FT{x}

这里 F T F_T FT 就是你想的那个意思。进而
Z S , k ′ ( y ) = ∑ T ⫅ S ∣ T ∣ = k [ n F T ( y ) − ∑ x ∈ T ⋯   ] Z_{S,k}'(y)=\sum_{T\subseteqq S}^{|T|=k}\left[nF_T(y)-\sum_{x\in T}\cdots\right] ZS,k(y)=TST=k[nFT(y)xT]

(由于后面那一坨太长,我就不写出来啦。直接用上面的式子替换得到的嘛。)
= n Z S , k ( y ) − ∑ x ∈ S y a x − 1 n a x − 1 ⋅ ( a x − 1 ) ! Z S − { x } , k − 1 ( y ) =nZ_{S,k}(y)-\sum_{x\in S}\frac{y^{a_x-1}}{n^{a_x-1}\cdot(a_x-1)!}Z_{S-\{x\},k-1}(y) =nZS,k(y)xSnax1(ax1)!yax1ZS{x},k1(y)

同理,这个是 O ( n ) \mathcal O(n) O(n) 转移,一共 O ( n m 2 n ) \mathcal O(n m2^n) O(nm2n) 个状态,复杂度仍然是
O ( n 2 m ⋅ 2 n ) \mathcal O(n^2m\cdot 2^n) O(n2m2n)

应该也可以过吧?但不是最优解。

思路贰

考场上被我直接否定的做法:“概率的分母在变,还是要不得。”——《论 O n e I n D a r k \sf OneInDark OneInDark 为什么弱》

留坑待填

考虑 k k k 个死掉的随从,按照时间顺序是第 x 1 , x 2 , x 3 , … , x k x_1,x_2,x_3,\dots,x_k x1,x2,x3,,xk 次攻击时被打死的。

那么这个方案的概率一定是
( 1 n − k ) m − x k ∏ i = 0 k − 1 ( 1 n − i ) x i + 1 \left(\frac{1}{n-k}\right)^{m-x_k}\prod_{i=0}^{k-1}\left(\frac{1}{n-i}\right)^{\large x_{i+1}} (nk1)mxki=0k1(ni1)xi+1

与每次具体是哪个随从受伤无关。有了这一点,我们考虑先勾勒出这个轮廓,然后再确定究竟是哪些随从。

f ( i , S ) f(i,S) f(i,S) 表示,考虑了前 i i i 次攻击,集合 S S S 中的随从死掉了,所有这种方案的概率和。如果下一步没有某个随从的死亡,直接乘 1 n − ∣ S ∣ \frac{1}{n-|S|} nS1 就行;如果有随从死亡,考虑在前面分配给这个随从 a j − 1 a_j-1 aj1 次攻击(因为这一次攻击是肯定打到它的)。所以
f ( i + 1 , S ) = f ( i , S ) n − ∣ S ∣ + ∑ j ∈ S ( i − ∑ x ∈ S x ≠ j a x a j − 1 ) f ( i , S − { j } ) n − ∣ S ∣ + 1 f(i+1,S)=\frac{f(i,S)}{n-|S|}+\sum_{j\in S}{i-\sum_{x\in S}^{x\ne j}a_x\choose a_j-1}\frac{f(i,S-\{j\})}{n-|S|+1} f(i+1,S)=nSf(i,S)+jS(aj1ixSx=jax)nS+1f(i,S{j})

显然这是 O ( n m ⋅ 2 n ) \mathcal O(nm\cdot 2^n) O(nm2n) 的。这就比 思路壹 少了一个 n n n

折半填坑

但是 f f f 数组是答案吗?当然不是。因为它还留着很多洞。

再定义 g ( i , S ) g(i,S) g(i,S) 表示,把 i i i 次攻击分配给 S S S 集合中的随从,使得每个人都没有死亡,方案数的和。这个其实就是一个背包。真正的答案是
∑ S ⫅ U ∣ S ∣ ⋅ f ( m , S ) ⋅ g ( m − ∑ x ∈ S a x , U − S ) \sum_{S\subseteqq\Bbb U}|S|\cdot f(m,S)\cdot g\left(m-\sum_{x\in S}a_x,\Bbb U-S\right) SUSf(m,S)g(mxSax,US)

直接求这个背包,复杂度是 O ( m 2 2 n ) \mathcal O(m^22^n) O(m22n),上天了!国集大佬 C h a r l o t t e ′ s    S p a c e \sf Charlotte's\;Space CharlottesSpace 写了 N T T \tt NTT NTT 优化,还是过不去。

注意到这样一个事实: g ( i , S ) g(i,S) g(i,S) 其实是 S S S 集合内的元素对应的指数型生成函数的乘积的 i i i 次项,即 g ( i , S ) = i ! ⋅ [ y i ] ∏ j ∈ S ( ∑ b < a j y b b ! ) g(i,S)=i!\cdot [y^i]\prod_{j\in S}(\sum_{b<a_j}\frac{y^b}{b!}) g(i,S)=i![yi]jS(b<ajb!yb),然后我们求的是 G ( U − S ) G(\Bbb U-S) G(US) 的某一项。

两个多项式相乘,只取一项时,复杂度是 O ( m ) \mathcal O(m) O(m) 的。于是乎,我们 m e e t - i n - m i d d l e \tt meet\text-in\text-middle meet-in-middle,就把复杂度优化到了
O ( m 2 2 n + m 2 n ) \mathcal O(m^2\sqrt{2^{n}}+m2^{n}) O(m22n +m2n)

另:此处也可以使用 T i w - A i r - O A O \sf Tiw\text-Air\text-OAO Tiw-Air-OAO 的求导大法。它更通用。

代码

这里就给出 思路贰 的代码了,毕竟好写。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxM = 200;
const int MaxN = 16;
const int Mod = 998244353;
int dp[MaxM+1][1<<MaxN];
int inv[MaxN+1], c[MaxM+1][MaxM+1];
int a[1<<MaxN], cnt[1<<MaxN];

const int HalfN = 8;
int g[2][1<<HalfN][MaxM+1];
int getG(int S,int m){
	int L = S&((1<<HalfN)-1);
	int R = S>>HalfN, ans = 0;
	for(int i=0; i<=m; ++i)
		ans = (ans+1ll*g[0][L][m-i]
			*g[1][R][i]%Mod*c[m][i])%Mod;
	return ans;
}

int main(){
	int n = readint(), m = readint();
	for(int i=(inv[1]=1)+1; i<=n; ++i)
		inv[i] = (0ll+Mod-Mod/i)*inv[Mod%i]%Mod;
	for(int i=0; i<=m; ++i)
	for(int j=c[i][0]=1; j<=i; ++j)
		c[i][j] = (c[i-1][j-1]+c[i-1][j])%Mod;
	for(int i=0; i<n; ++i)
		a[1<<i] = readint();
	for(int S=1; S!=(1<<n); ++S){
		a[S] = a[S^(S&-S)]+a[S&-S];
		cnt[S] = cnt[S^(S&-S)]+1;
	}
	dp[0][0] = 1; // nothing ever happens
	for(int i=1; i<=m; ++i)
	for(int S=0; S<(1<<n); ++S){
		if(a[S] > i) continue; // impossible
		dp[i][S] = 1ll*dp[i-1][S]*inv[n-cnt[S]]%Mod;
		for(int j=0; j<n; ++j) if(S>>j&1)
			dp[i][S] = (dp[i][S]+1ll*dp[i-1][S^(1<<j)]
				*c[i-1-a[S^(1<<j)]][a[1<<j]-1]%Mod
				*inv[n-cnt[S]+1])%Mod;
	}
	g[0][0][0] = g[1][0][0] = 1;
	for(int d=0; d<2; ++d)
	for(int S=1,id; S<(1<<HalfN); ++S){
		for(id=0; !(S>>id&1); ++id);
		g[d][S][0] = 1; // for sure
		for(int i=1; i<=m; ++i)
		for(int j=0; j<a[1<<(id+d*HalfN)]&&j<=i; ++j)
			g[d][S][i] = (g[d][S][i]+1ll*c[i][j]*
				g[d][S^(1<<id)][i-j])%Mod;
	}
	int ans = 0;
	for(int S=1; S<(1<<n); ++S){
		if(a[S] > m) continue; // impossible
		ans = (ans+1ll*cnt[S]*dp[m][S]%Mod
			*getG((1<<n)-1-S,m-a[S]))%Mod;
	}
	printf("%d\n",ans);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值