[CTSC2018]假面

一、题目

点此看题

二、解法

把原问题分成三部分解决:锁定;结界;期望生命值。

先考虑锁定怎么处理,设 d p [ i ] [ j ] dp[i][j] dp[i][j]为第 i i i个人剩 j j j点血的概率,转移就考虑当前损不损血,暴力转移即可,这一部分时间复杂度是 O ( n q ) O(nq) O(nq),可以接受。

那么期望生命值也就很好算了,概率 × \times ×贡献即可。

考虑结界怎么处理,首先我们需要知道死的概率 p p p(也就是 d p [ i ] [ 0 ] dp[i][0] dp[i][0]),我们先钦定一个人活着,然后对剩下的人跑 d p dp dp,设 f [ i ] [ j ] f[i][j] f[i][j]为前 i i i个人有 j j j个人活着的概率,转移直接暴力,跑完 d p dp dp之后,考虑有 j j j个人活着,并且当前的人也活着,再乘上 j + 1 j+1 j+1的逆元求和即可。

上面的做法时间复杂度是 O ( n 3 c ) O(n^3c) O(n3c)的,可以拿到 70 70 70分,发现上面算法低效的原因就是做了很多无用的 d p dp dp,我们可以先把所有人的 d p dp dp跑出来,因为 d p dp dp与顺序无关,每个人都可以当最后一个,可以靠虑退掉这个人 d p dp dp,这里需要分类 p = 0 p=0 p=0的情况。如果 p = 0 p=0 p=0,那么 f [ k − 1 ] [ i ] = f [ k ] [ i + 1 ] f[k-1][i]=f[k][i+1] f[k1][i]=f[k][i+1],否则根据转移方程来推: f [ k − 1 ] [ i ] = f [ k ] [ i ] − f [ k − 1 ] [ i − 1 ] × a l i v e d e a d f[k-1][i]=\frac{f[k][i]-f[k-1][i-1]\times alive}{dead} f[k1][i]=deadf[k][i]f[k1][i1]×alive,这样就可以优化到 O ( n 2 c ) O(n^2c) O(n2c)了。

口胡可能难以理解,请看代码 q w q qwq qwq

#include <cstdio>
#include <cstring>
#define int long long
const int MOD = 998244353;
const int M = 205;
int read()
{
    int x=0,flag=1;char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m[M],q,dp[M][M],f[M][M],g[M],a[M],inv[M];
//dp[i][j] 第i个剩j点血的概率(g辅助dp)
//f[i][j] 前i个有j个活着的概率
int qkpow(int a,int b)
{
    int r=1;
    while(b>0)
    {
        if(b&1) r=r*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return r;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        m[i]=read();
        dp[i][m[i]]=1;
    }
    q=read();
    for(int i=1;i<=n;i++)
        inv[i]=qkpow(i,MOD-2);
    while(q--)
    {
        int op=read(),k=read();
        if(op==0)
        {
            int u=read(),v=read(),p=u*qkpow(v,MOD-2)%MOD;
            memcpy(g,dp[k],sizeof dp[k]);
            for(int i=1;i<=m[k];i++)
                dp[k][i]=0;
            for(int i=1;i<=m[k];i++)
            {
                dp[k][i]+=g[i]*(1-p+MOD)%MOD;
                dp[k][i-1]+=g[i]*p%MOD;
            }
            for(int i=0;i<=m[k];i++)
                dp[k][i]%=MOD;
        }
        else
        {
            for(int i=1;i<=k;i++)
                a[i]=read();
            f[0][0]=1;
            for(int j=1;j<=k;j++)
            {
                int p=dp[a[j]][0];
                for(int l=0;l<=k;l++)
                {
                    f[j][l]=0;
                    if(l>0) f[j][l]=f[j-1][l-1]*(1-p+MOD)%MOD;
                    f[j][l]=(f[j][l]+f[j-1][l]*p%MOD)%MOD;
                }
            }
            for(int i=1;i<=k;i++)
            {
                int ans=0,d=dp[a[i]][0];
                int p=(1-dp[a[i]][0]+MOD)%MOD;
                if(d==0)
                {
                    for(int j=0;j<k;j++)
                        f[k-1][j]=f[k][j+1];
                }
                else
                {
                    d=qkpow(d,MOD-2);
                    f[k-1][0]=f[k][0]*d%MOD;
                    for(int j=1;j<k;j++)
                        f[k-1][j]=(f[k][j]-f[k-1][j-1]*p%MOD+MOD)%MOD*d%MOD;
                }
                for(int j=0;j<k;j++)
                {
                    ans=(ans+f[k-1][j]*p%MOD*inv[j+1]%MOD)%MOD;
                }
                printf("%lld ",ans);
            }
            puts("");
        }
    }
    for(int i=1;i<=n;i++)
    {
        int ans=0;
        for(int j=0;j<=m[i];j++)
            ans=(ans+dp[i][j]*j)%MOD;
        printf("%lld ",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值