【BZOJ5340】假面(CTSC2018)-概率DP

测试地址:假面
做法:本题需要用到概率DP。
首先,注意到血量很小,因此对于第一种操作,直接概率DP维护敌方单位在各个血量的概率即可,式子很简单相信大家都会,我就不写了,时间复杂度为 O(Qm) O ( Q m )
接下来,对于第二种操作,令第 i i 个单位的存活概率为pi,则有:
ansi=k1j=01j+1g(i,j) a n s i = ∑ j = 0 k − 1 1 j + 1 g ( i , j )
其中 g(i,j) g ( i , j ) 为除 i i 之外恰有j个单位存活的概率。我们怎么计算这个概率呢?注意到这些单位的安排顺序并不影响 g g 的值,因此我们要算第i个单位时,就把这个单位放到队列的最后,然后对前面进行概率DP。令 f(i,j) f ( i , j ) 为前 i i 个单位恰有j个存活的概率,则有:
f(i,j)=pif(i1,j1)+(1pi)f(i1,j) f ( i , j ) = p i f ( i − 1 , j − 1 ) + ( 1 − p i ) f ( i − 1 , j )
显然 g(i,j)=f(k1,j) g ( i , j ) = f ( k − 1 , j ) 。那么我们对每个单位都进行一次这样的DP,总的时间复杂度就是 O(Cn3) O ( C n 3 ) 的,可以拿到 70 70 分。
要进一步优化,首先要发现 f(k,j) f ( k , j ) 无论是在哪个单位的计算过程中都是相同的,而 f(k,j) f ( k , j ) 一层仅由 f(k1,j) f ( k − 1 , j ) 一层转移而来,那么我们可以尝试按照前面的状态转移方程倒推,于是有:
f(k1,j)=f(k,j)pkf(k1,j1)1pk f ( k − 1 , j ) = f ( k , j ) − p k f ( k − 1 , j − 1 ) 1 − p k
于是对于每个单位的计算,时间复杂度就从 O(n2) O ( n 2 ) 优化到了 O(n) O ( n ) 。然而肯定有同学注意到了,万一 pk=1 p k = 1 就不能使用上面的式子了,那要怎么办呢?注意到此时,有 f(k,j)=f(k1,j1) f ( k , j ) = f ( k − 1 , j − 1 ) ,也就是说 f(k1,j)=f(k,j+1) f ( k − 1 , j ) = f ( k , j + 1 ) ,直接计算即可。上面的所有递推式都没有写边界条件,相信大家可以自行脑补(实在不行就请看本人的代码…)。那么我们就得到了一个总时间复杂度为 O(Cn2) O ( C n 2 ) 的算法,可以通过此题。
我傻逼的地方:有个地方忘记取模,导致计算溢出……这应该是很低级的错误了……
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int n,m[210],q,pos[210];
ll inv[210]={0},t[210][110]={0},f[210][210]={0},g[210]={0};

ll power(ll a,ll b)
{
    ll s=1,ss=a;
    while(b)
    {
        if (b&1) s=s*ss%mod;
        ss=ss*ss%mod;b>>=1;
    }
    return s;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&m[i]);
        t[i][m[i]]=1;
        inv[i]=power(i,mod-2);
    }

    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        int op,id,k;
        ll u,v;
        scanf("%d",&op);
        if (!op)
        {
            scanf("%d%lld%lld",&id,&u,&v);
            ll p=u*power(v,mod-2)%mod;
            t[id][0]=(t[id][0]+p*t[id][1])%mod;
            for(int j=1;j<=m[id];j++)
                t[id][j]=(((1ll-p)*t[id][j]+p*t[id][j+1])%mod+mod)%mod;
        }
        else
        {
            scanf("%d",&k);
            f[0][0]=1;
            for(int j=1;j<=k;j++)
            {
                scanf("%d",&pos[j]);
                f[j][0]=f[j-1][0]*t[pos[j]][0]%mod;
                for(int p=1;p<=j;p++)
                    f[j][p]=((f[j-1][p]*t[pos[j]][0]+f[j-1][p-1]*(1ll-t[pos[j]][0]))%mod+mod)%mod;
            }
            for(int j=1;j<=k;j++)
            {
                ll ans=0;
                if (t[pos[j]][0])
                {
                    ll nowinv=power(t[pos[j]][0],mod-2);
                    g[0]=f[k][0]*nowinv%mod;
                    for(int p=1;p<k;p++)
                        g[p]=((f[k][p]-g[p-1]*(1ll-t[pos[j]][0]))%mod+mod)%mod*nowinv%mod;
                }
                else
                {
                    for(int p=0;p<k;p++)
                        g[p]=f[k][p+1];
                }
                for(int p=0;p<k;p++)
                    ans=(ans+inv[p+1]*g[p])%mod;
                printf("%lld ",(ans*(1ll-t[pos[j]][0])%mod+mod)%mod);
            }
            printf("\n");
        }
    }

    for(int i=1;i<=n;i++)
    {
        ll ans=0;
        for(ll j=1;j<=m[i];j++)
            ans=(ans+j*t[i][j])%mod;
        printf("%lld ",ans);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值