Description

猎人杀是一款风靡一时的游戏“狼人杀”的民间版本,他的规则是这样的:

一开始有 n个猎人,第 i 个猎人有仇恨度 wi。每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡。

然而向谁开枪也是有讲究的,假设当前还活着的猎人有\([i_1...i_m]\),那么有\(w_{i_k}\over \sum\limits_{j=1}^{m} w_{i_j}\)的概率是向猎人\(i_k\) 开枪

一开始第一枪由你打响,目标的选择方法和猎人一样(即有\(w_{i}\over \sum\limits_{j=1}^{m} w_{j}\)的概率射中第i个猎人)。由于开枪导致的连锁反应,所有猎人最终都会死亡,现在1号猎人想知道它是最后一个死的的概率。

对998244353取模

\(w_i>0,\sum w_i\leq 100000\)

Solution

首先有结论,我们假设可以对已经死亡的猎人开枪,对已经死亡猎人开枪之后继续开枪,那么问题是等价的。

这样就好做不少,因为每个人中枪的概率就固定了。

根据这个结论,我们来推一波式子。

我们可以将整个开枪过程看做是一个序列,每个数可以出现多次,每个数出现有概率,题目问的是1出现时其他所有数都已经出现过的概率。

考虑指数型生成函数,设\(t=\sum w_k\),容易得出除1号外i号猎人的EGF是$$\sum\limits_{j>0}{w_ijxj\over tji!}=e{w_ix\over t}-1$$

那么将这些猎人拼接,总的式子就是$$\prod\limits_{k=2}{n}(e{w_kx\over t}-1)$$

假设有3个猎人,2,3号猎人拼在一起就是\(e^{(w_2+w_3)x\over t}-e^{w_2x\over t}-e^{w_3x\over t}+1\)

对于每个EGF,它对总概率的贡献就是其系数之和

对于\(e^{px}\),将其系数求和(不考虑阶乘),就是等比数列求和的形式,可以得出和就是\(1\over 1-p\)

那么对于上面的式子,一样计算和,然后加到一起,最后再乘上\(w_1/t\)(最后一次要选上1号)

现在问题的关键就是要算上面的乘积的每一项\(e^{px},p\in[0,t]\)的系数

我们可以把每个\(e^{px}\)也看做多项式的一项,因为同是指数相加,可以构造多项式\(x^{w_k\over t}-1\),那么$$\prod\limits_{k=2}{n}(x{w_k\over t}-1)$$

的每一项\(x^{p}\)前的系数就是原式中每一个\(e^{px}\)的系数

可以先不看t,用分治NTT做,最后再算上。

总复杂度\(O(n\log^2 n)\)

Code

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define M 262144
#define L 18
#define mo 998244353
#define LL long long
#define N 100005
using namespace std;
LL wi[M+1],wg[M+1],a[M+1],b[M+1],c[M+1],ny,w[N];
int a1[N],bit[M+1],sz[N],n1,n,sm[N],l2[M+1],cf[L+1],sum;
LL ksm(LL k,LL n)
{
LL s=1;
for(;n;n>>=1,k=k*k%mo) if(n&1) s=s*k%mo;
return s;
}
void prp(int num)
{
fo(i,0,num-1) bit[i]=(bit[i>>1]>>1)|((i&1)<<(l2[num]-1));
fo(i,0,num) wi[i]=wg[M/num*i];
ny=ksm(num,mo-2);
}
void NTT(LL *a,bool pd,int num)
{
LL v,w;
fo(i,0,num-1) if(i<bit[i]) swap(a[i],a[bit[i]]);
for(int m=2,lim=num>>1,half=1;m<=num;half=m,m<<=1,lim>>=1)
{
fo(i,0,half-1)
{
w=(!pd)?wi[i*lim]:wi[num-i*lim];
for(int j=i;j<num;j+=m)
{
v=a[j+half]*w%mo;
a[j+half]=(a[j]-v+mo)%mo;
a[j]=(a[j]+v)%mo;
}
}
}
if(pd) fo(i,0,num-1) a[i]=a[i]*ny%mo;
}
void doit(int l,int r)
{
if(l==r) return;
int mi=sm[n],mid=l;
fo(j,l,r-1) if(max(sm[j]-sm[l-1],sm[r]-sm[j])<mi) mi=max(sm[j]-sm[l-1],sm[r]-sm[j]),mid=j;
doit(l,mid),doit(mid+1,r);
int num=cf[l2[sz[mid+1]+sz[l]+1]];
prp(num);
fo(i,0,num-1) b[i]=c[i]=0;
fo(i,0,sz[l]) b[i]=a[a1[l]+i];
fo(i,0,sz[mid+1]) c[i]=a[a1[mid+1]+i];
NTT(b,0,num),NTT(c,0,num);
fo(i,0,num-1) b[i]=b[i]*c[i]%mo;
NTT(b,1,num);
sz[l]+=sz[mid+1];
fo(i,0,sz[l]) a[a1[l]+i]=b[i];
}
int main()
{
cin>>n;
int l=-1;
cf[0]=1;
fo(i,1,18) cf[i]=(cf[i-1]<<1),l2[cf[i]]=i;
fod(i,M-1,2) if(!l2[i]) l2[i]=l2[i+1];
fo(i,1,n)
{
int c;
scanf("%d",&w[i]);
c=w[i],sum+=c;
if(i!=1)
{
a1[i]=++l;
a[l]=mo-1;
l+=c;
a[l]=1,sz[i]=c,sm[i]=sz[i]+sm[i-1];
}
}
wg[0]=1;
LL v=ksm(3,(mo-1)/M);
fo(i,1,M) wg[i]=wg[i-1]*v%mo;
doit(2,n);
LL ans=0;
fo(i,0,sm[n])
ans=(ans+a[i]*(LL)sum%mo*ksm(sum-i,mo-2)%mo+mo)%mo;
printf("%lld\n",ans*w[1]%mo*(LL)ksm(sum,mo-2)%mo);
}

【杂题】[LibreOJ 2541] 【PKUWC2018】猎人杀【生成函数】【概率与期望】的更多相关文章

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

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

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

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

  3. [PKUWC2018]猎人杀

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

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

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

  5. LOJ 2541 「PKUWC2018」猎人杀——思路+概率+容斥+分治

    题目:https://loj.ac/problem/2541 看了题解才会……有三点很巧妙. 1.分母如果变动,就很不好.所以考虑把操作改成 “已经选过的人仍然按 \( w_i \) 的概率被选,但是 ...

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

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

  7. [LOJ2541] [PKUWC2018] 猎人杀

    题目链接 LOJ:https://loj.ac/problem/2541 Solution 很巧妙的思路. 注意到运行的过程中概率的分母在不停的变化,这样会让我们很不好算,我们考虑这样转化:假设所有人 ...

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

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

  9. 题解-PKUWC2018 猎人杀

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

随机推荐

  1. Docker添加官方加速源(必须)

    在国内使用Docker必须用加速镜像不然的话无论是pull 官方的还是私有的镜像都会WAIT TIME EXCEED 下面给出macos的添加方式,非常简单 macOS 对于使用 macOS 的用户, ...

  2. python decimal和fractions模块

    1.简介 默认,浮点数学缺乏精确性 decimal 模块提供了一个 Decimal 数据类型用于浮点数计算.相比内置的二进制浮点数实现 float这个类型有助于金融应用和其它需要精确十进制表达的场合, ...

  3. CodeForces 681B Economy Game (暴力)

    题意:给定一个数,问能不能 找到非负 a, b, c,使得 a × 1 234 567 + b × 123 456 + c × 1 234 = n. 析:二重循环,去确定c. 代码如下: #inclu ...

  4. Object 和 JSON 区别联系

    JavaScript Object-based JavaScript is almost entirely object-based. Object name Object property name ...

  5. VC++中MessageBox的常见用法详解

    消息框是个很常用的控件,属性比较多,本文列出了它的一些常用方法,及指出了它的一些应用场合.         1.MessageBox("这是一个最简单的消息框!");        ...

  6. Vivado&ISE&Quartus II调用Modelsim级联仿真

    博主一直致力寻找高效的工作方式,所以一直喜欢折腾软件,从刚开始只用软件IDE自带的编辑器,到Notepad++,再到后来的Vim,从用ISE14.7自带的Isim仿真,到发现更好的Modelsim,再 ...

  7. .Net core 2.0的数据初始化

    在StartUp.cs里面,添加Seed方法 public static void Seed(IApplicationBuilder applicationBuilder) { using (var ...

  8. DOORS的引用类型

    DOORS的引用类型包括:Project: 项目引用Folder: 文件夹引用Item: 项目或文件夹之内的项(项目.文件夹.模块)Module: 打开模块的引用Object: 对象的引用 ...

  9. Java 使用json 做配置文件

    概述 经常会用到通过配置文件,去配置一些参数,java里面本来是有配置文件的,但是导入很麻烦的,自从我用了json之后,从此一切配置文件都见鬼去吧. 1.下载gson解析json文件的jar包     ...

  10. 为什么JavaScript要有null?(翻译)

    原文地址 JavaScript有不少怪癖和难以理解的地方.其中null& undefined就比较有意思.既然有了为什么JavaScript还要弄一个null? 相等比较 让我们开始由具有看看 ...