题目链接

LOJ:https://loj.ac/problem/2541

Solution

很巧妙的思路。

注意到运行的过程中概率的分母在不停的变化,这样会让我们很不好算,我们考虑这样转化:假设所有人都活着,然后随机选一个人,如果此人已死那就重新选一次。

假设当前活着的人集合为\(T\),那么射中第\(i\)个人的概率就是:

\[\sum_{i=0}^{\infty}\left(\frac{s_{all}-s_T}{s_{all}}\right)^i\frac{w_i}{s_{all}}=\frac{w_i}{s_T}
\]

其中\(s_p\)表示\(p\)集合的\(w\)总和,可以发现这样选的概率和原来是一样的。

我们考虑容斥,设\(f(T)\)表示至少\(T\)集合的人比\(1\)号后死,用一个很简单的容斥可以得到:

\[ans=\sum_{T}(-1)^{|T|}f(T)
\]

那么大力算可以得到\(f\):

\[\begin{align}f(T)&=\sum_{i=0}^{\infty}\left(\frac{s_{all}-s_T-w_1}{s_{all}}\right)^i\cdot \frac{w_1}{s_{all}}\\&=\frac{w_1}{w_1+s_T}\end{align}
\]

答案就是:

\[ans=\sum_T(-1)^{|T|}\frac{w_1}{w_1+s_T}
\]

注意到\(s\)至多只有\(1e5\),我们可以背包算出每个\(s_T\)出现了多少次,背包的时候顺便把容斥系数带上。

这样做是\(O(ns)\)的,显然\(T\)掉了。

但是我们可以用生成函数优化这个东西,直接就是:

\[\prod_{i=2}^{n}(1-x^{w_i})
\]

然后分治\(FFT\)优化就好了,复杂度\(O(n\log ^2 n)\)。

#include<bits/stdc++.h>
using namespace std; void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
} void print(int x) {
if(x<0) putchar('-'),x=-x;
if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');} #define lf double
#define ll long long #define pii pair<int,int >
#define vec vector<int > #define pb push_back
#define mp make_pair
#define fr first
#define sc second #define FOR(i,l,r) for(int i=l,i##_r=r;i<=i##_r;i++) const int maxn = 4e5+10;
const int inf = 1e9;
const lf eps = 1e-8;
const int mod = 998244353; int w[maxn],pos[maxn],N,bit,f[maxn],a[maxn],s[maxn],n,mxn; int add(int x,int y) {return x+y>=mod?x+y-mod:x+y;}
int del(int x,int y) {return x-y<0?x-y+mod:x-y;}
int mul(int x,int y) {return 1ll*x*y-1ll*x*y/mod*mod;} int qpow(int a,int x) {
int res=1;
for(;x;x>>=1,a=mul(a,a)) if(x&1) res=mul(res,a);
return res;
} void prepare(int t) {
for(N=1,bit=0;N<=t;N<<=1,bit++);mxn=N;w[0]=1,w[1]=qpow(3,(mod-1)/mxn);
for(int i=2;i<=N;i++) w[i]=mul(w[i-1],w[1]);
} void ntt_get(int t) {
for(N=1,bit=0;N<=t;N<<=1,bit++);
for(int i=1;i<N;i++) pos[i]=pos[i>>1]>>1|((i&1)<<(bit-1));
} void ntt(int *r,int op) {
for(int i=1;i<N;i++) if(pos[i]>i) swap(r[i],r[pos[i]]);
for(int i=1,d=mxn>>1;i<N;i<<=1,d>>=1)
for(int j=0;j<N;j+=i<<1)
for(int k=0;k<i;k++) {
int x=r[j+k],y=mul(r[i+j+k],w[k*d]);
r[j+k]=add(x,y),r[i+j+k]=del(x,y);
}
if(op==-1) {
reverse(r+1,r+N);int d=qpow(N,mod-2);
for(int i=0;i<N;i++) r[i]=mul(r[i],d);
}
} int get(int lt,int rt) {
int l=lt,r=rt,mid,ans=lt;
while(l<=r) {
mid=(l+r)>>1;
if(s[rt]-s[mid]>=s[mid]-s[lt-1]) l=mid+1,ans=mid;
else r=mid-1;
}return ans;
} void solve(int l,int r,int *t) {
if(l>r) return ;
if(l==r) {t[0]=1,t[a[l]]=mod-1;return ;}
int d=1<<((int)ceil(log2(s[r]-s[l-1]))+1);
int *sl=new int [d+10],*sr=new int [d+10],mid=get(l,r);
for(int i=0;i<=d+5;i++) sl[i]=sr[i]=0;
solve(l,mid,sl),solve(mid+1,r,sr);
ntt_get(d>>1);ntt(sl,1),ntt(sr,1);
for(int i=0;i<N;i++) t[i]=mul(sl[i],sr[i]);
ntt(t,-1);delete sl;delete sr;
} int main() {
read(n);for(int i=1;i<=n;i++) read(a[i]),s[i]=s[i-1]+a[i];
prepare(s[n]<<1);solve(2,n,f);int ans=0;
for(int i=0;i<=s[n];i++) ans=add(ans,mul(qpow(a[1]+i,mod-2),f[i]));
write(mul(ans,a[1]));
return 0;
}

[LOJ2541] [PKUWC2018] 猎人杀的更多相关文章

  1. LOJ2541 PKUWC2018猎人杀(概率期望+容斥原理+生成函数+分治NTT)

    考虑容斥,枚举一个子集S在1号猎人之后死.显然这个概率是w1/(Σwi+w1) (i∈S).于是我们统计出各种子集和的系数即可,造出一堆形如(-xwi+1)的生成函数,分治NTT卷起来就可以了. #i ...

  2. LOJ2541 PKUWC2018 猎人杀 期望、容斥、生成函数、分治

    传送门 首先,每一次有一个猎人死亡之后\(\sum w\)会变化,计算起来很麻烦,所以考虑在某一个猎人死亡之后给其打上标记,仍然计算他的\(w\),只是如果打中了一个打上了标记的人就重新选择.这样对应 ...

  3. [LOJ2541][PKUWC2018]猎人杀(容斥+分治+FFT)

    https://blog.csdn.net/Maxwei_wzj/article/details/80714129 n个二项式相乘可以用分治+FFT的方法,使用空间回收可以只开log个数组. #inc ...

  4. [PKUWC2018]猎人杀

    题解 感觉是一道神题,想不出来 问最后\(1\)号猎人存活的概率 发现根本没法记录状态 每次转移的分母也都不一样 可以考虑这样一件事情: 如果一个人被打中了 那么不急于从所有人中将ta删除,而是给ta ...

  5. 题解-PKUWC2018 猎人杀

    Problem loj2541 题意概要:给定 \(n\) 个人的倒霉度 \(\{w_i\}\),每回合会有一个人死亡,每个人这回合死亡的概率为 自己的倒霉度/目前所有存活玩家的倒霉度之和,求第 \( ...

  6. 洛谷 P5644 - [PKUWC2018]猎人杀(分治+NTT)

    题面传送门 很久之前(2020 年)就听说过这题了,这么经典的题怎么能只听说而亲自做一遍呢 首先注意到每次开枪打死一个猎人之后,打死其他猎人概率的分母就会发生变化,这将使我们维护起来非常棘手,因此我们 ...

  7. 【洛谷5644】[PKUWC2018] 猎人杀(容斥+生成函数+分治NTT)

    点此看题面 大致题意: 有\(n\)个人相互开枪,每个人有一个仇恨度\(a_i\),每个人死后会开枪再打死另一个还活着的人,且第一枪由你打响.设当前剩余人仇恨度总和为\(k\),则每个人被打中的概率为 ...

  8. 【LOJ2541】【PKUWC2018】猎人杀(容斥,FFT)

    [LOJ2541][PKUWC2018]猎人杀(容斥,FFT) 题面 LOJ 题解 这题好神仙啊. 直接考虑概率很麻烦,因为分母总是在变化. 但是,如果一个人死亡之后,我们不让他离场,假装给他打一个标 ...

  9. 「PKUWC2018」猎人杀

    「PKUWC2018」猎人杀 解题思路 首先有一个很妙的结论是问题可以转化为已经死掉的猎人继续算在概率里面,每一轮一直开枪直到射死一个之前没死的猎人为止. 证明,设所有猎人的概率之和为 \(W\) , ...

随机推荐

  1. FOREIGN KEY 外键约束; UNIQUE和PRIMARY KEY 主键约束、CREATE INDEX建立索引的使用

    1)foreign key 是个约束,意思是说如果你给A字段设置了外键约束,以后你要往A字段插入数据,这个数据一定是要在foreign key 后面跟的那个字段中存在的值.这个的意义就是约束了数据的完 ...

  2. Spark-Streaming kafka count 案例

    Streaming 统计来自 kafka 的数据,这里涉及到的比较,kafka 的数据是使用从 flume 获取到的,这里相当于一个小的案例. 1. 启动 kafka Spark-Streaming ...

  3. elasticsearch: can not run elasticsearch as root

    进入bin目录启动elasticsearch: lunadeMacBook-Air:elasticsearch-6.1.0 luna$ cd bin/ lunadeMacBook-Air:bin lu ...

  4. 第07组 Alpha冲刺(6/6)

    队名:摇光 队长:杨明哲 组长博客:求戳 作业博客:求再戳 队长:杨明哲 过去两天完成了哪些任务 文字/口头描述:博客生成的逻辑 展示GitHub当日代码/文档签入记录:(组内共用,已询问过助教小姐姐 ...

  5. 剑指offer:字符流中第一个不重复的字符

    题目描述: 请实现一个函数用来找出字符流中第一个只出现一次的字符.例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g".当从该字符流中读 ...

  6. [转]Windows内存堆内容整理总结

    在系统安全研究中,堆,是一个极其重要的内存区域以及研究的热点.堆,区别于栈区.全局数据区以及代码区,它的主要作用是允许程序在运行时动态地申请某个大小的内存空间.本文将从宏观到微观,简单梳理总结一下Wi ...

  7. cocos creator策略类手游开发教程

    目录 大小6.83GB,Mp4格式,语言:中文 扫码时备注或说明中留下邮箱 付款后如未回复请至https://shop135452397.taobao.com/ 联系店主

  8. Python面向对象编程核心思想

    原文地址https://blog.csdn.net/weixin_42134789/article/details/80194788 https://blog.csdn.net/happyjxt/ar ...

  9. Oracle定时任务执行存储过程备份日志记录表

    写在前面 需求 1.备份系统日志表T_S_LOG, 按照操作时间字段OPERATETIME, 将每天的日志增量备份到另一张表. 思路 1.创建一张数据结构完全相同的表T_S_LOG_BAK作为备份表 ...

  10. arcpy实例教程-上游流域下游流域查找

    arcpy实例教程-上游流域下游流域查找 商务合作,科技咨询,版权转让:向日葵,135-4855_4328,xiexiaokui#qq.com 功能:对于选定某一流域,查找其直接(一次)的上游流域和下 ...