【XSY3241】暴风士兵(stormtrooper)(多项式分治,期望)

本文介绍了如何使用分治策略和快速傅里叶变换(FFT)来高效解决一类概率计算问题。通过将操作转化为生成函数,然后进行多项式乘法和减法卷积,可以将时间复杂度降低到O(nlog^2n)。文章详细展示了算法的实现过程,包括分治递归、NTT变换和减法卷积等步骤,对于处理大规模概率查询非常有用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设一个人被扣了 i i i 滴血的概率为 p i p_i pi,设 c i = e x p − i c_i=exp-i ci=expi 且只有 c 0 , c 1 , ⋯   , c e x p c_0,c_1,\cdots,c_{exp} c0,c1,,cexp 有值,那么题目就是在问 ∑ i = 0 e x p c i p i \sum\limits_{i=0}^{exp}c_ip_i i=0expcipi

我们设 p i p_i pi 的生成函数为 P ( x ) P(x) P(x),那么第 i i i 次操作相当于将 P ( x ) P(x) P(x) 乘上 ( p i x + ( 1 − p i ) ) (p_ix+(1-p_i)) (pix+(1pi))

那么题目第 t t t 次询问要求的即为 ∑ i = 0 e x p c i [ x i ] P ( x ) \sum\limits_{i=0}^{exp}c_i[x^i]P(x) i=0expci[xi]P(x),其中 P ( x ) = ∏ i = 1 t ( p i x + ( 1 − p i ) ) P(x)=\prod\limits_{i=1}^t(p_ix+(1-p_i)) P(x)=i=1t(pix+(1pi))

每次求出 P ( x ) P(x) P(x) 并暴力统计答案显然是不现实的。

P ( x ) = A ( x ) B ( x ) P(x)=A(x)B(x) P(x)=A(x)B(x) A ( x ) = ∏ i = 1 k ( p i x + ( 1 − p i ) ) = ∑ i = 0 k a i x i A(x)=\prod\limits_{i=1}^k(p_ix+(1-p_i))=\sum\limits_{i=0}^ka_ix^i A(x)=i=1k(pix+(1pi))=i=0kaixi B ( x ) = ∏ i = k + 1 t ( p i x + ( 1 − p i ) ) = ∑ i = 0 t − k b i x i B(x)=\prod\limits_{i=k+1}^{t}(p_ix+(1-p_i))=\sum\limits_{i=0}^{t-k}b_ix^i B(x)=i=k+1t(pix+(1pi))=i=0tkbixi

那么:
a n s t = ∑ i = 0 e x p c i [ x i ] P ( x ) = ∑ i = 0 e x p c i [ x i ] A ( x ) B ( x ) = ∑ i = 0 e x p c i ∑ j = 0 i a j b i − j = ∑ i = 0 e x p b i ∑ j = 0 e x p − i c j + i a j = ∑ i = 0 e x p c i ′ [ x i ] B ( x ) \begin{aligned} ans_t&=\sum\limits_{i=0}^{exp}c_i[x^i]P(x)\\ &=\sum_{i=0}^{exp}c_i[x^i]A(x)B(x)\\ &=\sum_{i=0}^{exp}c_i\sum_{j=0}^ia_jb_{i-j}\\ &=\sum_{i=0}^{exp}b_i\sum_{j=0}^{exp-i}c_{j+i}a_j\\ &=\sum_{i=0}^{exp}c_i'[x^i]B(x) \end{aligned} anst=i=0expci[xi]P(x)=i=0expci[xi]A(x)B(x)=i=0expcij=0iajbij=i=0expbij=0expicj+iaj=i=0expci[xi]B(x)
(其中 c i ′ = ∑ j = 0 e x p − i c j + i a j c_i'=\sum\limits_{j=0}^{exp-i}c_{j+i}a_j ci=j=0expicj+iaj

然后就可以分治了:假设当前区间为 [ l , r ] [l,r] [l,r],长度为 m = r − l + 1 m=r-l+1 m=rl+1,我们可以先递归 [ l , m i d ] [l,mid] [l,mid],算出这段区间的答案,并算出次数为 m 2 \dfrac{m}{2} 2m A ( x ) A(x) A(x),再用它和 c i c_i ci 做减法卷积 O ( m log ⁡ m ) O(m\log m) O(mlogm) 算出 c i ′ c_i' ci 的前 m 2 \dfrac{m}{2} 2m 项,然后再把 c i ′ c_i' ci 代入作为新的 c i c_i ci 递归 [ m i d + 1 , r ] [mid+1,r] [mid+1,r],算出这段区间的答案,并算出次数为 m 2 \dfrac{m}{2} 2m B ( x ) B(x) B(x),最后 O ( m log ⁡ m ) O(m\log m) O(mlogm) 计算出 P ( x ) = A ( x ) B ( x ) P(x)=A(x)B(x) P(x)=A(x)B(x)

总时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

#include<bits/stdc++.h>

#define LN 19
#define N 100010

using namespace std;

namespace modular
{
	const int mod=998244353;
	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
	inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
}using namespace modular;

inline int poww(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1) ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

typedef vector<int> poly;

int lans,n;
int rev[N<<2],w[LN][N<<2][2];
int A[N<<2],B[N<<2];
poly p[N<<2],c[N<<2];

void init(int limit)
{
	for(int bit=0,mid=1;mid<limit;bit++,mid<<=1)
	{
		int len=mid<<1;
		int gn=poww(3,(mod-1)/len);
		int ign=poww(gn,mod-2);
		int g=1,ig=1;
		for(int j=0;j<mid;g=mul(g,gn),ig=mul(ig,ign),j++)
			w[bit][j][0]=g,w[bit][j][1]=ig;
	}
}

void NTT(int *a,int limit,int opt)
{
	opt=(opt<0);
	for(int i=0;i<limit;i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)*(limit>>1));
	for(int i=0;i<limit;i++)
		if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int bit=0,mid=1;mid<limit;bit++,mid<<=1)
	{
		for(int i=0,len=mid<<1;i<limit;i+=len)
		{
			for(int j=0;j<mid;j++)
			{
				int x=a[i+j],y=mul(w[bit][j][opt],a[i+mid+j]);
				a[i+j]=add(x,y),a[i+mid+j]=dec(x,y);
			}
		}
	}
	if(opt)
	{
		int tmp=poww(limit,mod-2);
		for(int i=0;i<limit;i++)
			a[i]=mul(a[i],tmp);
	}
}

void solve(int k,int l,int r)
{
	if(l==r)
	{
		int pi=read();
		pi=add(pi,lans);
		p[k].push_back(dec(1,pi));
		p[k].push_back(pi);
		printf("%d\n",lans=(add(mul(p[k][0],c[k][0]),mul(p[k][1],c[k][1]))));
		return;
	}
	int mid=(l+r)>>1,lc=k<<1,rc=k<<1|1;
	int len=r-l+1,lenl=mid-l+1,lenr=r-mid;
	for(int i=0;i<=lenl;i++) c[lc].push_back(c[k][i]);
	solve(lc,l,mid);
	int limit=1;
	while(limit<=len+lenl) limit<<=1;
	for(int i=0;i<=len;i++) A[i]=c[k][i];
	for(int i=0;i<=lenl;i++) B[i]=p[lc][lenl-i];
	NTT(A,limit,1),NTT(B,limit,1);
	for(int i=0;i<limit;i++) A[i]=mul(A[i],B[i]);
	NTT(A,limit,-1);
	for(int i=0;i<=lenr;i++) c[rc].push_back(A[lenl+i]);
	for(int i=0;i<limit;i++) A[i]=B[i]=0;
	solve(rc,mid+1,r);
	limit=1;
	while(limit<=len) limit<<=1;
	for(int i=0;i<=lenl;i++) A[i]=p[lc][i];
	for(int i=0;i<=lenr;i++) B[i]=p[rc][i];
	NTT(A,limit,1),NTT(B,limit,1);
	for(int i=0;i<limit;i++) A[i]=mul(A[i],B[i]);
	NTT(A,limit,-1);
	for(int i=0;i<=len;i++) p[k].push_back(A[i]);
	for(int i=0;i<limit;i++) A[i]=B[i]=0;
}

int main()
{
	lans=read(),n=read();
	int limit=1;
	while(limit<=(n<<1)) limit<<=1;
	init(limit);
	for(int i=0;i<=lans;i++) c[1].push_back(lans-i);
	for(int i=lans+1;i<=n;i++) c[1].push_back(0);
	solve(1,1,n);
	return 0;
}
/*
1 1
0
*/
/*
918 991
456307063
488721210
886185325
243931909
217329246
856712605
*/
/*
98908 99965
775551600
543754136
259120750
821986384
362863140
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值