[PKUWC2018]猎人杀 解题报告

题目

\(n\) 个猎人,第 \(i\) 个猎人有一个仇恨度 \(w_i\) ,被打中的概率是 \(\frac{w_i}{sum}\)\(sum\) 是活着的猎人的仇恨度总和。

一开始由第一个猎人开出一枪,问第一个猎人是最后死的概率。

\(n\le 10^5 , sum\le 10^5\)

其实我以前在搞约翰夫抽杀时想过和这个一模一样的题,当时感觉十分不可做,现在看来是我太菜了,没想到真的有这道题。

每次打中一个猎人总的仇恨度会发生变化,感觉十分不可做,所以要考虑转化一下问题。

假设总的仇恨度为 \(s_0\) ,目前活着的人的仇恨度总和为 \(s_1\) ,则第 \(i\) 个人被打中的概率为 \(p_i=\frac{w_i}{s_1}\) ,为了更好的做题,考虑把分母转化为 \(s_0\) ,那么有 \(p_i=\frac{w_i+(s_0-s_1)p_i}{s_0}\) ,再把 \(p_i=\frac{w_i}{s_1}\) 代到前面变成 \(\frac{w_i}{s_1}=\frac{w_i+(s_0-s_1)p_i}{s_0}\) ,化简可得 \(p_i=\frac{w_i}{s_0}\)

所以题目可以转化成每个人被打中的概率是 \(\frac{w_i}{s_0}\) ,可以打中死人,问第一个猎人是最后死的概率。

这种反直觉的概率果然不是给我这种粗人玩的Orz

但是最后死的概率并不好求,考虑运用套路,通过容斥将限制放宽,假设 \(S\) 是一个以猎人为元素的集合, \(F(x)\) 是第一个猎人死后剩下猎人集合包含 \(S\) 的概率,那么有 \(Ans=\sum\limits (-1)^{|S|} F(S)\)

考虑枚举第一个猎人是第几个死的,可以得到 \(F(S)=\sum\limits_{i=0}^{\infty} (\frac{s_0-w_1-sum(S)}{s_0})^i\frac{w_1}{s_0}\) ,通过等比数列求和公式和对极限的运用,能化简成 \(F(S)=\frac{w_1}{w_1+sum(S)}\) ,所以有 \(Ans=\sum\limits (-1)^{|S|} \frac{w_1}{w_1+sum(S)}\)

直接枚举集合再考虑优化不太可能,所以要另寻蹊径,发现数据范围中有 \(s_0\le 10^5\) 这一条,而且 \(F(S)\) 也只有 \(sum(S)\) 这一变量,考虑枚举 \(sum(S)\) 来求答案。

假设 \(G(x)=\sum\limits_{sum(S)=x} (-1)^{|S|}\) ,那么有 \(Ans=\sum\limits_{i=1}^{s_0} \frac{w_1G(i)}{w_1+i}\)

这个 \(G(x)\) 的限制条件显然可以用生成函数来求解,由于每个猎人最多选一个,所以对于第 \(i\) 个猎人,他的生成函数就只有第 \(0\) 项是 \(1\) 和第 \(w_i\) 项是 \(-1\) ,将所有的生成函数乘起来的第 \(x\) 项正是 \(G(x)\)

这个东西可以用堆+NTT或者分治+NTT做到 \(\mathcal{O(n\log^2 n)}\)

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int M=2e5+5,JYY=998244353;

int n,Sum=0,Ans=0,w[M];
int read(){
    int x=0,y=1;char ch=getchar();
    while(ch<'0'||ch>'9') y=(ch=='-'?-1:1),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*y;
}

int qpow(int x,int y){
    int res=1;x%=JYY,y%=(JYY-1);
    for(;y;x=x*x%JYY,y>>=1) if(y&1) res=res*x%JYY;
    return res;
}

namespace Poly{
    int rev[M],wn[35];
    void getwn(){ for(int i=0;i<=25;i++) wn[i]=qpow(3,(JYY-1)/(1<<i));}
    void NTT(int f[],int L,int inv){
        for(int i=0;i<L;i++) if(i<rev[i]) swap(f[i],f[rev[i]]);
        for(int mid=1,id=1;mid<L;mid<<=1,id++)
            for(int R=(mid<<1),j=0;j<L;j+=R)
                for(int k=0,w=1;k<mid;k++,w=w*wn[id]%JYY){
                    int u=f[j+k],v=w*f[j+k+mid]%JYY;
                    f[j+k]=(u+v)%JYY;
                    f[j+k+mid]=(u-v+JYY)%JYY;
                }
        if(inv==-1){
            reverse(f+1,f+L);int ny=qpow(L,JYY-2);
            for(int i=0;i<L;i++) f[i]=f[i]*ny%JYY;
        }
    }
    void poly_mul(int f[],int g[],int h[],int L1,int L2){
        int len=1,L=0;static int a[M],b[M];
        for(;len<L1+L2;len<<=1) L++;
        for(int i=0;i<len;i++){
            rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
            a[i]=(i<L1?f[i]:0),b[i]=(i<L2?g[i]:0);
        }
        NTT(a,len,1),NTT(b,len,1);
        for(int i=0;i<len;i++) h[i]=a[i]*b[i];
        NTT(h,len,-1);
        for(int i=L1+L2-1;i<len;i++) h[i]=0;
    }
}using namespace Poly;

vector<int> cun[M];
void work(int u,int l,int r){
    if(l==r){
        cun[u].push_back(1);
        for(int i=1;i<w[l];i++) cun[u].push_back(0);
        cun[u].push_back(JYY-1);
        return ;
    }
    static int f[M],g[M],h[M];
    int mid=(l+r)>>1;
    work(u<<1,l,mid),work(u<<1|1,mid+1,r);
    int szl=cun[u<<1].size(),szr=cun[u<<1|1].size();
    for(int i=0;i<szl;i++) f[i]=cun[u<<1][i];
    for(int i=0;i<szr;i++) g[i]=cun[u<<1|1][i];
    poly_mul(f,g,h,szl,szr);
    for(int i=0;i<szl+szr-1;i++) cun[u].push_back(h[i]);
}

void solve(){
    n=read();
    for(int i=1;i<=n;i++) Sum+=w[i]=read();
    getwn();work(1,2,n);
    for(int i=0;i<=Sum-w[1];i++) Ans=(Ans+cun[1][i]*w[1]%JYY*qpow(w[1]+i,JYY-2)%JYY)%JYY;
    printf("%lld\n",Ans);
}

signed main(){
    // freopen("shuju.in","r",stdin);
    solve();
}
posted @ 2021-01-06 23:23  Dabuliuzp  阅读(129)  评论(4编辑  收藏  举报
/* */ 返回顶端