P4564 [CTSC2018]假面

没想到能在短短的3天碰见两次这种长得像背包的 dp 的撤销问题,故有此文章。

题意

\(n\) 个人,每个人有 \(m[i]\) 的初始血量。

有两种操作:第一种以概率 \(p\) 对指定人攻击,有 \(p\) 的概率生命值 \(-1\)\(1 - p\) 的概率生命值不变。第二种给定一群目标,对这些人释放一次只能命中一个人的攻击,以活人的人头数等概率攻击,问每个人被攻击的概率是多少。

最后一个问题,问所有操作结束后,每个人血量的期望。

Solution

首先可以发现,第二种操作不会影响到第一种操作。

接下来逐步分析。

操作一:

我们令q[i][j]表示第 \(i\) 个人的血量为 \(j\) 的概率。

可以发现,每次攻击都会向下转移。

q[i][j] *= (1-p)

q[i][j] += q[i][j+1] * p

分别对应当前血量在攻击中存活,或者 j+1 的血量被攻击。

于是我们处理完了操作一。

操作二:

发现这个问题不好求的地方就在于,我不确定死了几个,活了几个。

那么考虑搞一个这个东西出来,令 g[i][j] 表示除了第 \(i\) 个人,活了 \(j\) 个的概率。

那么第 \(i\) 个人的答案就是 \(h[i] = alive(i)\sum_{j=0}^{k-1}\cfrac{g[i][j]}{j+1}\)
,其中 alive(i)表示i存活概率

发现这个东西依然不好求,那我们再来一个 dp。

考虑令 f[i][j] 表示前 \(i\) 个人,活了 \(j\) 个的概率,那么显然对于一个人 \(x\) ,我只要把他扔到最后面就好了,那么它的 \(g[x][j]\) 就是 \(f[k-1][j]\)

对于 \(f\) ,可以写出来转移

f[i][j] = f[i-1][j] * dead(i) + f[i-1][j-1] * alive(i)

这个东西可以用背包滚动掉第一维,也就是倒过来枚举。

f[j] = f[j] * dead(i) + f[j-1] * alive(i)

于是你就有了 \(O(n^3)\)\(70\) 哩!

继续优化。

考虑我们现在是把每个 \(x\) 扔到最后面,求出来了前面的,但是背包的过程可以不分先后,那么我们可不可以先求出来整体的,然后每次扣掉第 \(i\) 个呢?

当然可以哩!

我愿称这一类套路为背包滴撤销。

首先我们可以给一些公式上的推导。

\[f[i][j] = f[i-1][j] \times dead(i) + f[i-1][j-1] \times alive(i) \]

\[f[i-1][j] = \cfrac{f[i][j] - f[i-1][j-1]\times alive(i)}{dead(i)} \]

注意在 \(dead(i) = 0\) 的时候没有意义,但就 \(f[i-1][j] = f[i][j+1]\),所以不用怕。

是不是看起来非常合理,感觉我撤销了,但我其实不太懂这种推导,我比较喜欢暴力用手模拟一下过程,以下默认滚动掉第一维。

  • \(i = 0:\) \(f[0], f[1], f[2]\)
  • \(i = 1:\) \(f[0]d[1],f[1]d[1] + f[0]a[1]\)\(f[2]d[1] + f[1]a[1]\)
  • \(i = 2:\) \(f[0]d[1]d[2]\)\((f[1]d[1] + f[0]a[1])d[2] + f[0]d[1]a[2]\)\((f[2]d[1] + f[1]a[1])d[2] + (f[1]d[1] + f[0]a[1])d[1]\)

那么我们假设只有这 \(2\) 个东西,现在求出了包含所有人的 \(dp\) ,考虑如何扣掉和 第 \(1\) 个人有关的转移,也就是去除所有 \(d[1],a[1]\)

回到了前面推的式子了,这回我们顺推,考虑得到所有的 \(g[j]\),注意也是滚掉第一维的,这里代表考虑除了第 \(1\) 个人有 \(j\) 个人存活的概率。

开始模拟!

首先对于 \(g[0]\) 显然就是 \(\cfrac{f[0]}{d[1]}\),此时我们得到的 \(g[0] = f[0]d[2]\)

注意,接下来 \(g\) 数组实际上是变成了 \(f_{i-1}\) ,然后我们继续用 \(f,g\) 得到新的 \(g\)

于是你观察 \(f[1]\) 的式子,你发现 \(\cfrac{f[1] -g[0]a[1] }{d[1]}\) 居然写出来正好消掉了所有滴 \(a[1],d[1]\) ,好神奇啊!!

再来看 \(f[2]\) ,我们发现 \(\cfrac{f[2] - g[1]a[1]}{d[1]}\) 居然依旧完美消去了 \(a[1],d[1]\) ,至此,相信大家能理解这种撤销过程的正确性了,非常神奇滴。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
    x=0;char ch=getchar();bool f=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int mod = 998244353;
const int N = 205;
using ll = long long;
ll f[N],g[N],h[N],t[N],q[N][N],Inv[N];
int n,k,a[N];
inline int ksm(int x,int y){
    ll res = 1;
    while(y){
        if(y & 1)res = res * x % mod;
        x = 1ll * x * x % mod;
        y >>= 1;
    }
    return res;
}
inline int inv(int x){
    return ksm(x,mod - 2);
}
inline void op0(int i,int p){
    int _p = (1 - p + mod) % mod;
    rep(j,0,a[i]){
        j > 0 ? q[i][j] = q[i][j] * _p % mod : 0;
        j < a[i] ? q[i][j] = (q[i][j] + q[i][j + 1] * p) % mod : 0;
    }
}
inline void op1(){
    memset(f,0,sizeof(f));
    memset(h,0,sizeof(h));
    memset(g,0,sizeof(g));
    f[0] = 1;
    rep(i,1,k)
        rrep(j,i,0)
            f[j] = (f[j] * q[t[i]][0] + (j ? f[j - 1] * (1 - q[t[i]][0]) : 0)) % mod;
    rep(i,1,k){
        if(!q[t[i]][0])Rep(j,0,k)h[i] += f[j + 1] * Inv[j + 1] % mod;
        else{
            int tmp = inv(q[t[i]][0]);
            Rep(j,0,k){
                g[j] = (f[j] - (j ? g[j - 1] * (1 - q[t[i]][0]) : 0)) % mod * tmp % mod;
                h[i] += g[j] * Inv[j + 1] % mod;
            }
        }
        h[i] %= mod;
        h[i] = h[i] * (1 - q[t[i]][0]) % mod;
        if(h[i] < 0)h[i] += mod;
    }
    rep(i,1,k)printf("%lld ",h[i]);
    puts("");
}
signed main(){
    read(n);
    rep(i,1,n)read(a[i]),q[i][a[i]] = 1,Inv[i] = inv(i);
    int Q;
    read(Q);
    while(Q--){
        int op;
        read(op);
        if(!op){
            int id,u,v;
            read(id,u,v);
            u = 1ll * u * inv(v) % mod;
            op0(id,u);
        }
        else{
            read(k);
            rep(i,1,k)read(t[i]);
            op1();
        }
    }
    rep(i,1,n){
        ll res = 0;
        rep(j,0,a[i])res += j * q[i][j] % mod;
        printf("%lld ",res % mod);
    }
}
posted @ 2022-09-28 08:30  Xu_brezza  阅读(29)  评论(0编辑  收藏  举报