zoukankan      html  css  js  c++  java
  • [CTSC 2018]假面

    Description

    题库链接

    (n) 个敌方单位,初始第 (i) 个单位的血量为 (m_i) 。共 (Q) 次操作,分两种:

    1. 对某一个单位以 (p) 的概率造成 (1) 点伤害。
    2. (k) 个指定的敌方单位施放技能,只能命中恰好 (1) 个敌方单位。命中每个存活的敌方单位的概率是相等的(也就是说已经死亡的敌方单位不会有任何影响)。求命中各敌人的概率分别是多少。该类操作不会超过 (C) 次。

    最后要求每个敌方单位的血量的期望。

    (n leq 200 , Q leq 200000 , C leq 1000 , m_i leq 100)

    Solution

    不妨记 (pk_{i,j}) 表示第 (i) 个单位还剩血量为 (j) 时的概率。这样是可以用 (O(Q imes m_i)) 来维护的。

    (pk) 数组我们可以计算出第 (i) 个单位存活概率为 (p_i)

    得到这个概率,我们可以用 (O(Cn^3))( ext{dp}) (背包)来统计答案。

    (f_{i,j}) 表示前 (i) 个人中存活了 (j) 个的概率。通过改变转移顺序第一维可以搞掉。

    考虑优化,注意到可以逆推,及不用算每个人的概率时剩下的人重新求一次。大致是:

    假设放第 (i) 个人时, (f) 数组是:

    [x_0,x_1,x_2,x_3,cdots]

    那么第 (i) 个人转移后的状态为:

    [x_0',x_1',x_2',x_3',cdots=x_0(1-p_i),x_0p_i+x_1(1-p_i),x_1p_i+x_2(1-p_i),x_2p_i+x_3(1-p_i),cdots]

    显然逆推回去 (x_0=frac{x_0'}{1-p_i}) ,剩下的满足:

    [x_i=frac{x_i'-x_{i-1}p_i}{1-p_i}]

    那么就可以逆背包过程了,可以优化到 (O(Cn^2))

    Code

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 200+5, M = 100+5, yzh = 998244353;
    
    int n, m[N], pk[N][M], u, v, q, op, p, id, pa[N], k, lst[N], inv[N], f[N], invpa[N];
    
    int quick_pow(int a, int b) {
        int ans = 1;
        while (b) {
            if (b&1) ans = 1ll*ans*a%yzh;
            a = 1ll*a*a%yzh, b >>= 1;
        }
        return ans;
    }
    int cal(int x) {
        int ans = 0;
        for (int i = 0; i < k; i++) (ans += 1ll*inv[i+1]*pa[x]%yzh*f[i]%yzh) %= yzh;
        return ans;
    }
    void work() {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d", &m[i]), pk[i][m[i]] = 1, pa[i] = 1;
        inv[0] = inv[1] = 1; for (int i = 2; i <= n; i++) inv[i] = -1ll*yzh/i*inv[yzh%i]%yzh;
        scanf("%d", &q);
        while (q--) {
            scanf("%d", &op);
            if (op == 0) {
                scanf("%d%d%d", &id, &u, &v); p = 1ll*u*quick_pow(v, yzh-2)%yzh;
                pk[id][0] = (pk[id][0]+1ll*pk[id][1]*p%yzh)%yzh;
                for (int i = 1; i <= m[id]; i++)
                    pk[id][i] = (1ll*pk[id][i]*(1-p)%yzh+1ll*pk[id][i+1]*p%yzh)%yzh;
                pa[id] = 1-pk[id][0]; invpa[id] = quick_pow(pk[id][0], yzh-2);
            }else {
                scanf("%d", &k); f[0] = 1;
                for (int i = 1; i <= k; i++) scanf("%d", &lst[i]), f[i] = 0;
                for (int i = 2; i <= k; i++) {
                    for (int j = i-1; j >= 1; j--)
                        f[j] = (1ll*(1-pa[lst[i]])*f[j]%yzh+1ll*pa[lst[i]]*f[j-1]%yzh)%yzh;
                    f[0] = 1ll*f[0]*(1-pa[lst[i]])%yzh;
                }
                printf("%d ", (cal(lst[1])+yzh)%yzh);
                for (int i = 2; i <= k; i++) {
                    if ((pa[lst[i]]+yzh)%yzh != 1) {
                        f[0] = 1ll*f[0]*invpa[lst[i]]%yzh;
                        for (int j = 1; j < k; j++)
                            f[j] = 1ll*(1ll*f[j]-1ll*pa[lst[i]]*f[j-1]%yzh)%yzh*invpa[lst[i]]%yzh;
                    }else for (int i = 0; i < k; i++) f[i] = f[i+1];
                    for (int j = k-1; j >= 1; j--)
                        f[j] = (1ll*(1-pa[lst[i-1]])*f[j]%yzh+1ll*pa[lst[i-1]]*f[j-1]%yzh)%yzh;
                    f[0] = 1ll*f[0]*(1-pa[lst[i-1]])%yzh;
                    printf("%d ", (cal(lst[i])+yzh)%yzh);
                }
                puts("");
            }
        }
        for (int i = 1; i <= n; i++) {
            int ans = 0;
            for (int j = 1; j <= m[i]; j++) (ans += 1ll*j*pk[i][j]%yzh) %= yzh;
            printf("%d ", (ans+yzh)%yzh);
        }
    }
    int main() {work(); return 0; }
  • 相关阅读:
    进程的两种开启方法,进程的常用方法,进程的常用属性
    并发编程简介
    周鸿祎:互联网成功十大案例
    sed用法详解
    awk与sed:一个关于多行处理的例子
    igmpproxy源代码学习——igmpProxyInit()
    获取网络接口信息——ioctl()函数与结构体struct ifreq、 struct ifconf
    unix网络编程——ioctl 函数的用法详解
    九大排序算法再总结
    浅谈《剑指offer》原题:不使用条件、循环语句求1+2+……+n
  • 原文地址:https://www.cnblogs.com/NaVi-Awson/p/9186066.html
Copyright © 2011-2022 走看看