litble的成(tui)长(fei)史

苟...苟活者在淡红的血色中,会依稀看见微茫的希望

洛谷P4564/loj2552 假面 简单dp

题目分析

走在去北京八十中食堂的路上,听到pyh大佬和yyj大佬讨论,说这题是普及组难度的。以下为他们的论据:
首先发现目标的血量很少,我们可以设f(i,j)表示第i个目标剩余j血量的概率,对于每一个“锁定”技能,O(血量)更新一次,是一个普及组难度的dp。复杂度是O(n2)的(此处的n是指和n一个数量级)

void boom(int x,int p1) {
    int p2=(1-p1+mod)%mod;//没打中的概率
    for(RI i=0;i<=m[x];++i) {
        if(i) f[x][i]=1LL*p2*f[x][i]%mod;//对于已死的情况额外考虑
        if(i<m[x]) f[x][i]=(f[x][i]+1LL*f[x][i+1]*p1%mod)%mod;
    }
}

于是第二问就做完了……并且也知道了每一个目标存活的概率。
现在开始看第一问,设alive(i)表示目标i存活的概率,die(i)表示目标i死亡的概率。对于一个结界技能,假设g(t,i)表示除了目标t以外的目标,存活i个的概率。那么目标t被打中的概率为:

alive(t)i=0k1g(t,i)i+1

发现把g设得具体一点,即前t个目标,存活i个的概率。由于已知每一个目标存活和死亡的概率,所以用一个普及组难度的dp可以求出来。然后g(k1,i)就是除了目标t以外存活i个的概率了。
这样一个dp是O(n2)的,对于每一个指定目标求一次,复杂度O(n3),过不了。
考虑直接算出所有指定目标存活i个的概率,也就是g(k,i),然后倒推。
由于:
g(k,i)=g(k1,i1)alive(k)+g(k1,i)die(k)

所以:
g(k1,i)=g(k,i)g(k1,i1)alive(k)die(k)

当然了,当die(k)=0时,g(k1,i)=g(k,i+1),也可求出。
这样我们可以先O(n2)算出所有指定目标存活i个的概率,然后对于每一个目标O(n)倒推得答案,复杂度就是O(n2)的。
三个dp均为普及组难度,综上,本题是一道普及组难度的题目。
这个结论一点猫病也没有

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
const int mod=998244353,N=205;
int n,Q;
int f[N][105],t[N],g1[N],g2[N],inv[N],m[N];
int ksm(int x,int y) {
    int re=1;
    for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
    return re;
}
void boom(int x,int p1) {
    int p2=(1-p1+mod)%mod;//没打中的概率//没打中的概率
    for(RI i=0;i<=m[x];++i) {
        if(i) f[x][i]=1LL*p2*f[x][i]%mod;//对于已死的情况额外考虑
        if(i<m[x]) f[x][i]=(f[x][i]+1LL*f[x][i+1]*p1%mod)%mod;
    }
}
void query() {
    int k=read();
    g1[0]=1;
    for(RI i=1;i<=k+1;++i) g1[i]=0;
    for(RI i=1;i<=k;++i) {
        t[i]=read();
        int p2=f[t[i]][0],p1=(1-p2+mod)%mod;
        for(RI j=i;j>=0;--j)//普及难度dp求所有目标中存活j个的概率,使用了滚动数组的方法
            g1[j]=((j?1LL*g1[j-1]*p1%mod:0)+1LL*g1[j]*p2%mod)%mod;
    }
    for(RI i=1;i<=k;++i) {
        int kans=0;
        if(!f[t[i]][0]) for(RI j=0;j<k;++j) g2[j]=g1[j+1];//不会死的情况
        else {//倒推
            int kl=ksm(f[t[i]][0],mod-2),kp=(1-f[t[i]][0]+mod)%mod;
            for(RI j=0;j<k;++j)
                g2[j]=(1LL*(g1[j]-(j?1LL*g2[j-1]*kp%mod:0))*kl%mod+mod)%mod;
        }
        for(RI j=0;j<k;++j) kans=(kans+1LL*g2[j]*inv[j+1]%mod)%mod;//统计答案
        kans=1LL*kans*(1-f[t[i]][0]+mod)%mod;
        printf("%d ",kans);
    }
    puts("");
}
int main()
{
    int bj,x,u,v;
    n=read();
    for(RI i=1;i<=n;++i) m[i]=read(),f[i][m[i]]=1;
    inv[1]=1;for(RI i=2;i<=n;++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
    Q=read();
    while(Q--) {
        bj=read();
        if(bj==0) x=read(),u=read(),v=read(),boom(x,1LL*u*ksm(v,mod-2)%mod);
        else query();
    }
    for(RI i=1;i<=n;++i) {
        int ans=0;
        for(RI j=1;j<=m[i];++j) ans=(ans+1LL*j*f[i][j]%mod)%mod;
        printf("%d ",ans);
    }
    return 0;
}
阅读更多
版权声明:这篇文章的作者是个蒟蒻,没有转载价值,如果要转说一下好了 https://blog.csdn.net/litble/article/details/80335787
文章标签: 简单dp
个人分类: 动态规划
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

洛谷P4564/loj2552 假面 简单dp

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭