bzoj5340/loj2552「CTSC2018」假面(期望与概率+背包dp)

这签到题送我见祖宗了啊qaq
全世界都A了这题,像我这样只拿了10分的傻子怕是不多了吧。

我们考虑怎么求最后的期望生命值。
因为减到0血就不减了,所以我们不能直接每次期望算,只好考虑一个背包转移。
dp[i][j]表示i减了j滴血的概率。减了K[i]滴血就代表i死了。
每次攻击时对x做一个O(mi)的背包转移即可。
最后对每个人枚举减j滴血的情况计算即可。

我们考虑“结界”询问。
即就是问活着k个人的概率是多少。
对于一个人x,他的答案就是
k=0m1f[k]p(x)1k+1 ∑ k = 0 m − 1 f [ k ] ∗ p ( x ) ∗ 1 k + 1
其中f[k]表示除了x之外的那些人中活k个人的概率。p(x)表示x存活的概率。
这样我们对每个人都背包一遍。复杂度 O(Cn3+Qm) O ( C n 3 + Q m )
理论上只能通过70%的数据。

我们考虑怎么优化,背包转移其实是可逆的。
我们考虑背包的转移方程:
f[i]=f[i1](1p)+f[i]p f ′ [ i ] = f [ i − 1 ] ∗ ( 1 − p ) + f [ i ] ∗ p
那么我们就有
f[i]=f[i]f[i1](1p)p f [ i ] = f ′ [ i ] − f [ i − 1 ] ∗ ( 1 − p ) p

因此我们先n个全背包一遍。
然后每次处理一个人x时把它拿掉,得到不含他的背包即可。
注意处理p=0的情况。
复杂度 O(Cn2+Qm) O ( C n 2 + Q m )

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 210
#define mod 998244353
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,dp[N][110],K[N],a[N],f[N],inv[N];//dp[i][j],i这个人目前减了j滴血的概率
inline int ksm(int x,int k){
    int res=1;for(;k;k>>=1,x=(ll)x*x%mod) if(k&1) res=(ll)res*x%mod;return res;
}
inline void inc(int &x,int y){x+=y;x%=mod;}
int main(){
//  freopen("a.in","r",stdin);
    n=read();inv[1]=1;
    for(int i=2;i<=n;++i) inv[i]=(ll)inv[mod%i]*(mod-mod/i)%mod;
    for(int i=1;i<=n;++i) K[i]=read(),dp[i][0]=1;
    int owo=read();
    while(owo--){
        int op=read();
        if(!op){
            int x=read(),y=read();y=(ll)y*ksm(read(),mod-2)%mod;
            for(int i=K[x]-1;i>=0;--i) inc(dp[x][i+1],(ll)dp[x][i]*y%mod),dp[x][i]=(ll)dp[x][i]*(1-y+mod)%mod;
        }else{
            int m=read();for(int i=1;i<=m;++i) a[i]=read();memset(f,0,sizeof(f));f[0]=1;
            for(int i=1;i<=m;++i){
                int y=dp[a[i]][K[a[i]]];
                for(int j=i-1;j>=0;--j) inc(f[j+1],(ll)f[j]*(1-y+mod)%mod),f[j]=(ll)f[j]*y%mod;
            }for(int i=1;i<=m;++i){
                int y=dp[a[i]][K[a[i]]],lst=1,res=0,invy=ksm(y,mod-2);
                for(int j=1;j<=m;++j) if(j!=i) lst=(ll)lst*dp[a[j]][K[a[j]]]%mod;
                for(int j=0;j<m;++j){
                    inc(res,(ll)(1-y+mod)*lst%mod*inv[j+1]%mod);
                    lst=y?(f[j+1]-(ll)lst*(1-y+mod)%mod)*invy%mod:f[j+2];lst%=mod;if(lst<0) lst+=mod;
                }printf("%d ",res);
            }puts("");
        }
    }for(int i=1;i<=n;++i){
        int res=K[i];
        for(int j=0;j<=K[i];++j) inc(res,-(ll)j*dp[i][j]%mod);if(res<0) res+=mod;
        printf("%d ",res);
    }return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值