「PKUWC2018」猎人杀(分治NTT+概率期望)

Description

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

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

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

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

答案对 \(998244353\) 取模。

【输入格式】
第一行一个正整数 \(n\) ;

第二行 \(n\) 个正整数,第 \(i\) 个正整数表示 \(w_i\)。

【输出格式】
输出一个非负整数表示答案。

【输入样例】

3
1 1 2

【输出样例】

915057324

【样例解释】
答案是 \(\frac{2}{4}×\frac{1}{2}+\frac{1}{4}×\frac{2}{3}=\frac{5}{12}\)。

【数据规模与约定】
对于 \(10\%\) 的数据,有 \(1\leq n\leq 10\)。

对于 \(30\%\) 的数据,有 \(1\leq n\leq 20\)。

对于 \(50\%\) 的数据,有 \(1\leq \sum\limits_{i=1}^{n}w_i\leq 5000\)。

另有 \(10\%\) 的数据,满足 \(1\leq w_i\leq 2\),且 \(w_1=1\)。

另有 \(10\%\) 的数据,满足 \(1\leq w_i\leq 2\),且 \(w_1=2\)。

对于 \(100\%\) 的数据,有 \(w_i>0\),且 \(1\leq \sum\limits_{i=1}^{n}w_i \leq 100000\)。

Solution

考虑容斥,即枚举强制在 \(1\) 号之后死的人。设 \(T\) 为枚举到的人的集合,\(S\) 为 \(T\) 中的 \(w_i\) 之和。

考虑怎么求 \(T\) 中的人都在 \(1\) 号之后死的概率。可以将它们合并成 \(0\) 号猎人,\(w_0=S\)。那么现在 \(\lceil\) \(0\) 号在 \(1\) 号之后死的概率 \(\rfloor\) 就是 \(\lceil\) \(T\) 中的人都在 \(1\) 号之后死的概率 \(\rfloor\)。显然 \(0\) 号和 \(1\) 号谁先死不受其它猎人影响,那么 \(\lceil\) \(0\) 号在 \(1\) 号之后死的概率 \(\rfloor\) 就是 \(\frac{w_1}{S+w_1}\),所以 \(\lceil\) \(T\) 中的人都在 \(1\) 号之后死的概率 \(\rfloor\) 也是 \(\frac{w_1}{S+w_1}\)。

集合 \(T\) 对答案的贡献为 \((-1)^{|T|}×\frac{w_1}{S+w_1}\)。

发现 \(\sum w_i \leq 10^5\),考虑对于每个 \(S\),求出 \(b_S\) 表示满足\(w_i\) 之和为 \(S\) 的集合 \(T\) 的 \((-1)^T\) 之和。 那么 \(ans=\sum b_S×\frac{w_1}{S+w_1}\)。

显然 \(b_S\) 就是多项式 \(\Pi _{i=2}^n(1-x^{w_i})\) 中 \(x^S\) 项的系数,分治 \(\text{NTT}\) 即可。

设 \(m=\sum_{i=1}^n w_i\),时间复杂度 \(O(m \log m)\)。

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

template <class t>
inline void read(t & res)
{
    char ch;
    while (ch = getchar(), !isdigit(ch));
    res = ch - 48;
    while (ch = getchar(), isdigit(ch))
    res = res * 10 + (ch ^ 48);
}

const int e = 2e5 + 5, mod = 998244353;
vector<int>g[e];
int rev[e], n, ans, val[e], lim;

inline int ksm(int x, int y)
{
    int res = 1;
    while (y)
    {
        if (y & 1) res = (ll)res * x % mod;
        y >>= 1;
        x = (ll)x * x % mod;
    }
    return res;
}

inline void upt(int &x, int y)
{
    x = y;
    if (x >= mod) x -= mod;
}

inline void fft(int *a, int n, int op)
{
    int i, j, k, r = (op == 1 ? 3 : (mod + 1) / 3);
    for (i = 0; i < n; i++)
    if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (k = 1; k < n; k <<= 1)
    {
        int w0 = ksm(r, (mod - 1) / (k << 1));
        for (i = 0; i < n; i += (k << 1))
        {
            int w = 1;
            for (j = 0; j < k; j++)
            {
                int b = a[i + j], c = (ll)w * a[i + j + k] % mod;
                upt(a[i + j], b + c);
                upt(a[i + j + k], b + mod - c);
                w = (ll)w * w0 % mod;
            }
        }
    }
}

inline void modify(int *a, int *b, int la, int lb)
{
    int i;
    fft(a, lim, 1);
    fft(b, lim, 1);
    for (i = 0; i < lim; i++) a[i] = (ll)a[i] * b[i] % mod;
    fft(a, lim, -1);
    int tot = ksm(lim, mod - 2);
    for (i = 0; i < la + lb - 1; i++) a[i] = (ll)a[i] * tot % mod;
}

inline void solve(int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    solve(l, mid); solve(mid + 1, r);
    int i, la = g[l].size(), lb = g[mid + 1].size();
    static int a[e], b[e];
    int k = 0; lim = 1;
    while (lim < la + lb - 1) lim <<= 1, k++;
    for (i = 0; i < lim; i++)
    rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << k - 1), a[i] = b[i] = 0;
    for (i = 0; i < la; i++) a[i] = g[l][i];
    for (i = 0; i < lb; i++) b[i] = g[mid + 1][i];
    modify(a, b, la, lb);
    g[l].resize(la + lb - 1);
    for (i = 0; i < la + lb - 1; i++) g[l][i] = a[i];
}

int main()
{
    int i, sum = 0;
    read(n);
    for (i = 1; i <= n; i++) read(val[i]), sum += val[i];
    sum -= val[1];
    g[1].push_back(1);
    for (i = 2; i <= n; i++)
    {
        g[i].resize(val[i] + 1);
        g[i][0] = 1;
        g[i][val[i]] = mod - 1;
    }
    solve(1, n);
    for (i = val[1]; i <= sum + val[1]; i++)
    {
        int x = i - val[1], inv = ksm(i, mod - 2);
        ans = (ans + (ll)g[1][x] * inv) % mod;
    }
    ans = (ll)ans * val[1] % mod;
    cout << ans << endl;
    fclose(stdin);
    fclose(stdout);
    return 0;
}

原文地址:https://www.cnblogs.com/cyf32768/p/12196025.html

时间: 2024-10-31 04:09:58

「PKUWC2018」猎人杀(分治NTT+概率期望)的相关文章

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后:

「PKUWC2018」猎人杀(概率+容斥+分治NTT)

https://loj.ac/problem/2541 很有意思的一道题目. 直接去算这题话,因为分母会变,你会发现不管怎么样都要枚举顺序. 考虑把题目转换,变成分母不会变的,即对于一个已经删过的,我们不把它从分母中剔除,但是,每一次的选择需要一直选直到选了一个没有被删过的. 然后再考虑怎么计算,这时就可以容斥了: 1既然要最后删除,我们枚举一个集合S一定在它之后被删,其它的随意. 设\(sw\)为\(\sum_{i\in S}w[i]\),\(W=\sum_{i=1}^n w[i]\) 最后答

loj2541 「PKUWC2018」猎人杀

https://loj.ac/problem/2541 自己是有多菜啊,10天前做的题,当时还是看了题解,还让NicoDafaGood同学给我讲了一下. 而我现在忘得一干二净,一点都想不起来了…… 主要是当时听懂了就打了,没有总结啊. 我们发现,我们设集合$A$的$w$之和是$S_A$ 那么一个集合$A$在1之后死的概率是$\frac{w_1}{S_A+w_1}$. 为什么呢. 虽然每次选下一个会死的人,是从没死的人中选,但是实际上,也可以是所有人中选,如果选到了死了的人就继续选. 记得很久以前

Loj #2542. 「PKUWC2018」随机游走

Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次询问给定一个集合 \(S\),求如果从 \(x\) 出发一直随机游走,直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步. 特别地,点 \(x\)(即起点)视为一开始就被经过了一次. 答案对 $998244353 $ 取模. 输入格式 第一行三个正整数 \(n,Q,x\). 接下来 \(

【PKUWC2018】猎人杀

题目描述 题目分析 设\(W=\sum\limits_{i=1}^nw_i\),\(A=\sum\limits_{i=1}^nw_i[i\ is\ alive]\),\(P_i\)为下一个打中\(i\)的概率. 如果开枪打中了已经死亡的猎人,我们可以视作再开一枪,这样就不会产生影响,因此有 \[ \begin{split} P_i&=\frac{W-A}{W}P_i+\frac{w_i}W\移项得\ P_i&=\frac{w_i}{A} \end{split} \] 考虑容斥,枚举\(S\

loj2537 「PKUWC2018」Minimax 【概率 + 线段树合并】

题目链接 loj2537 题解 观察题目的式子似乎没有什么意义,我们考虑计算出每一种权值的概率 先离散化一下权值 显然可以设一个\(dp\),设\(f[i][j]\)表示\(i\)节点权值为\(j\)的概率 如果\(i\)是叶节点显然 如果\(i\)只有一个儿子直接继承即可 如果\(i\)有两个儿子,对于儿子\(x\),设另一个儿子为\(y\) 则有 \[f[i][j] += f[x][j](1 - p_i)\sum\limits_{k > j}f[r][k] + f[x][j]p_i\sum\

loj2537. 「PKUWC2018」Minimax

题意 略. 题解 首先设\(f_{x, c}\)表示以\(x\)为根的子树内,最终取到了\(c\)的概率.可以列出转移方程(假设有两个孩子\(u, v\)) \[ \begin{aligned} f_{x, c} = & f_{u, c} * (p * v子树中最终权值小于c的概率 + (1 - p) * v子树中最终权值大于c的概率) \+ & f_{v, c} * (p * u子树中最终权值小于c的概率 + (1 - p) * u子树中最终权值大于c的概率) \\end{aligned

loj#2537. 「PKUWC2018」Minimax

传送门 感觉我去pkuwc好像只有爆零的份-- 设\(f_{u,i}\)表示\(u\)取到\(i\)的概率,那么有如下转移 \[f_{u,i}=f_{ls,i}(p_u\sum_{j<i}f_{rs,j}+(1-p_u)\sum_{j>i}f_{rs,j})+\\f_{rs,i}(p_u\sum_{j<i}f_{ls,j}+(1-p_u)\sum_{j>i}f_{ls,j})\] 然后用线段树合并即可,最后在根节点的线段树上\(dfs\)统计答案 //minamoto #inclu

「PKUWC2018」随机算法

传送门 Description 我们知道,求任意图的最大独立集是一类NP完全问题,目前还没有准确的多项式算法,但是有许多多项式复杂度的近似算法. 例如,小 C 常用的一种算法是: 对于一个 \(n\) 个点的无向图,先等概率随机一个 $1\ldots n $的排列 \(p[1\ldots n]\). 维护答案集合 \(S\),一开始 \(S\) 为空集,之后按照 \(i=1\ldots n\) 的顺序,检查 \(\{p[i]\}\cup S\) 是否是一个独立集,如果是的话就令 \(S=\{p[