嘘~ 正在从服务器偷取页面 . . .

CF248E Piglet's Birthday 题解


CF248E Piglet's Birthday 题解

题目链接:Piglet's Birthday

题意

小猪今天过生日了。他的朋友小熊维尼想要给他最好的礼物 —— 一个蜂蜜罐。当然,小熊明白他不可能把整个罐子都送给小猪。事实上,他很可能会把罐子里的蜂蜜全都吃掉。因此,当小熊计划在路上吃一点小吃时,罐子最开始应该有尽可能多的蜂蜜。

前一天,小熊维尼补充了他的蜂蜜储备。小熊维尼家里有 \(n\) 个货架,每个货架上可能有一些,也可能一个蜂蜜罐都没有。在一天中,小熊维尼来到蜂蜜货架上 \(q\) 次;在第 \(i\) 次来到货架 \(u_{i}\),从中取出一些罐子 \(k_{i}\),尝试了每个罐子的蜂蜜后,将所有这些罐子放在某个货架 \(v_{i}\) 上。当小熊选择罐子时,他遵循自己的直觉。这意味着在货架 \(u_{i}\) 上所有 \(k_{i}\) 个罐子的集合中,他等概率地选择一个。

现在小熊记得他对蜂蜜罐执行的所有动作。他想要带着前一天没有尝试过的罐子参加聚会。为此,他必须知道没有一罐蜂蜜尝试过的货架数量 \(m\) 的数学期望。为了更好地评估自己的机会,小熊维尼希望在执行每个动作后都知道 \(m\) 的值。

你的任务是编写一个程序来为他找到这些值。

省流

给定 \(n\) 个货架,初始时每个上面有 \(a_i\) 个蜜罐。

\(q\) 次操作,每次操作形如 \(u,v,k\),表示从货架 \(u\) 上任意选择 \(k\) 个蜜罐试吃(吃过的也还能吃),吃完后把这 \(k\) 个蜜罐放到 \(v\) 货架上去。

每次操作完之后回答所有蜜罐都被试吃过的货架数量的期望。

输入格式

输入的第一行包含一个单独的数字 \(n\)\(1 \leq n \leq 10^{5}\))——小熊家的货架数量。第二行包含 \(n\) 个整数 \(a_{i}\)\(1 \leq i \leq n\)\(0 \leq a_{i} \leq 100\))——第 \(i\) 个货架上的蜂蜜罐数量。

接下来一行包含一个整数 \(q\)\(1 \leq q \leq 10^{5}\))——小熊前一天所做动作的数量。然后是 \(q\) 行,其中第 \(i\) 行描述了按时间顺序发生的一个事件;该行包含三个整数 \(u_{i}\)\(v_{i}\)\(k_{i}\)\(1 \leq u_{i},v_{i} \leq n\)\(1 \leq k_{i} \leq 5\))——小熊从哪个货架取出罐子,小熊尝试了每个罐子后将罐子放在哪个货架上,以及小熊尝试的罐子数量,分别。

货架上的罐子编号从 \(1\)\(n\)。保证小熊维尼从未尝试过从货架上取更多的罐子。

输出格式

对于每个小熊的动作,打印出该动作执行时的数学期望值 \(m\)。每个值的相对或绝对误差不得超过 \(10^{-9}\)

可以发现,每个架子上没吃过的蜜罐 \(🍯\) 永远不会超过 \(100\)​ 个。

记没吃过的蜜罐初始\(a_i\) 个,并记当前吃过和没吃过的一共有 \(b_i\) 个。

\(f(i,j)\) 表示第 \(i\) 个架子上还剩 \(j\) 个蜜罐没吃过的概率。(老套路了,期望转概率计算)

考虑转移,对于操作 \((u,v,w)\) ,有 \[ f(u,j) = \dfrac{\sum_{k=j}^{\min \{j + w,~b_u\}}f(u,k)\dbinom{k}{k-j}\dbinom{b_u-k}{w-(k-j)}}{\dbinom{b_i}{w}} \] 注意这道题没有限制 \(b_i\) 的大小,因此组合数的数组要开到 C[500000 + 15][5 + 5] ,并使用 double

时间复杂度 \(\mathcal{O}(500\times q)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))
#define M ((int)(5e5 + 15))

int a[N], now[N];
double res, f[N][114], C[M][14];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cout << fixed << setprecision(10);
    int n, q; cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i]; now[i] = a[i];
        f[i][a[i]] = 1; res += f[i][0];
    }
    for(int i = 0; i < M - 5; i++) C[i][0] = 1;
    for(int i = 1; i < M - 5; i++)
        for(int j = 1; j <= min(i, 5ll); j++) C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
    cin >> q;
    for(int i = 1, u, v, w; i <= q; i++)
    {
        cin >> u >> v >> w; res -= f[u][0];
        for(int j = 0; j <= a[u]; j++)
        {
            double sum = 0;
            for(int k = j; k <= min(j + w, now[u]); k++)
                sum += f[u][k] * C[k][k - j] * C[now[u] - k][w - (k - j)];
            f[u][j] = sum / C[now[u]][w];
        }
        res += f[u][0]; now[u] -= w; now[v] += w;
        cout << res << '\n';
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/hjf292vl


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录