洛谷 P5644 - [PKUWC2018]猎人杀(分治+NTT)
很久之前(2020 年)就听说过这题了,这么经典的题怎么能只听说而亲自做一遍呢
首先注意到每次开枪打死一个猎人之后,打死其他猎人概率的分母就会发生变化,这将使我们维护起来非常棘手,因此我们考虑做一个转化:每次随便从全集中选出一个猎人(不管死的活的),如果它是活的就将它射死。假设现在死了的猎人的 wiwi 值之和为 TT,所有猎人的 wiwi 值之和为 UU,那么精通无穷级数的同学应该不难推出,对于某个还活着的猎人 jj,射到的第一个活着的猎人是 jj 的概率就是 ∞∑i=0(TU)i·wjU=UU−T·wjU=wjU−T∞∑i=0(TU)i⋅wjU=UU−T⋅wjU=wjU−T,刚好就是题目中的式子。
这样一来我们就大可不必考虑“每一枪射到的猎人必须是活的”这个限制了,接下来考虑原问题。考虑容斥(没想到*1),我们考虑钦定一个集合 S(1∉S)S(1∉S) 并令 SS 中的猎人必须在 11 之后死,我们记这样的概率为 p(S)p(S),那么答案显然就是 ∑1∉Sp(S)(−1)|S|∑1∉Sp(S)(−1)|S|。考虑这个 p(S)p(S) 是个什么东西,按照上面的转化,SS 中的猎人在 11 之后死即意味着在打死 11 之前选择的猎人都不在 SS 中,那么我们可以枚举打死 11 之前开了多少枪,设这个数是 cc,方便起见我们假设 X=∑x∈SwxX=∑x∈Swx,那么可列出方程 p(S)=∞∑c=0(U−X−w1U)c·w1U=UX+w1·w1U=w1X+w1p(S)=∞∑c=0(U−X−w1U)c⋅w1U=UX+w1⋅w1U=w1X+w1。
琢磨清楚 p(S) 是个什么东西之后,最后一步就是计算上面那个式子了。暴力枚举 S 显然 T 飞,想也别想了。不过一个 observation 是 p(S) 的表达式只与 S 中所有元素的 w 值之和 X 有关,因此我们考虑枚举 X,即 ans=∑Xw1X+w1∑S(−1)|S|[∑x∈Swx=X],也就是说如果我们能求出所有满足 ∑x∈Swx=X 的 (−1)|S| 之和那这题就搞定了。这东西怎么求呢?这东西看起来好像有点眼熟,wx 之和等于 X 可以看作……系数之和等于 X,对!生成函数(想不到 *2,u1s1 中考结束后 wtm 简直像个 sb)。我们令 F(x)=n∏i=2(1−xwi),那么这东西就是 [xX]F(x),由于 n∑i=1wi≤105,因此可以分治+NTT(为什么是“分治+NTT”而不是“分治 NTT”呢?因为这里的分治不是 cdq 分治)求出 F(x),时间复杂度 nlog2n
const int MAXN=1e5; const int MAXP=1<<18; const int pr=3; const int MOD=998244353; const int ipr=(MOD+1)/3; int n,a[MAXN+5]; int qpow(int x,int e){ int ret=1; for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD; return ret; } int rev[MAXP+5]; void NTT(vector<int> &a,int len,int type){ int lg=31-__builtin_clz(len); for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<lg-1); for(int i=0;i<len;i++) if(i<rev[i]) swap(a[i],a[rev[i]]); for(int i=2;i<=len;i<<=1){ int W=qpow((type<0)?ipr:pr,(MOD-1)/i); for(int j=0;j<len;j+=i){ for(int k=0,w=1;k<(i>>1);k++,w=1ll*w*W%MOD){ int X=a[j+k],Y=1ll*a[(i>>1)+j+k]*w%MOD; a[j+k]=(X+Y)%MOD;a[(i>>1)+j+k]=(X-Y+MOD)%MOD; } } } if(!~type){ int ivn=qpow(len,MOD-2); for(int i=0;i<len;i++) a[i]=1ll*a[i]*ivn%MOD; } } vector<int> conv(vector<int> a,vector<int> b,int len){ int LEN=1;while(LEN<a.size()+b.size()) LEN<<=1; a.resize(LEN,0);b.resize(LEN,0);NTT(a,LEN,1);NTT(b,LEN,1); for(int i=0;i<LEN;i++) a[i]=1ll*a[i]*b[i]%MOD; NTT(a,LEN,-1);while(a.size()>len) a.pop_back();return a; } vector<int> solve(int l,int r){ if(l==r){ vector<int> res(a[l]+1,0); res[a[l]]=MOD-(res[0]=1); return res; } int mid=l+r>>1; vector<int> L=solve(l,mid); vector<int> R=solve(mid+1,r); return conv(L,R,L.size()+R.size()-1); } int main(){ scanf("%d",&n);if(n==1) return puts("1")&0;int sum=0; for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=!!(i^1)*a[i]; vector<int> res=solve(2,n);int ans=0; for(int i=0;i<=sum;i++) ans=(ans+1ll*a[1]*qpow(a[1]+i,MOD-2)%MOD*res[i])%MOD; printf("%d\n",ans); return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· .NET 进程 stackoverflow异常后,还可以接收 TCP 连接请求吗?
· 本地部署 DeepSeek:小白也能轻松搞定!
· 基于DeepSeek R1 满血版大模型的个人知识库,回答都源自对你专属文件的深度学习。
· 在缓慢中沉淀,在挑战中重生!2024个人总结!
· Tinyfox 简易教程-1:Hello World!
· 大人,时代变了! 赶快把自有业务的本地AI“模型”训练起来!