P4564 [CTSC2018]假面(期望)

P4564 [CTSC2018]假面

首先容易看出结界技能对第二问敌方剩余生命值期望没有影响。

如何求出第 i i i个人的剩余生命值期望?
只需要根据 E i = ∑ j = 0 a i j × f i , j E_i=\sum_{j=0}^{a_i}j×f_{i,j} Ei=j=0aij×fi,j
预处理 f i , j f_{i,j} fi,j:第 i i i个人的剩余生命值为 j j j的期望( a i a_i ai表示最初生命值)

由于每次锁定技能只能明确对一名地方单位造成攻击( p p p概率击中而 q q q概率不中),每次只需要 O ( a i ) O(a_i) O(ai)的代价维护 f i , j f_{i,j} fi,j总的时间复杂度 O ( Q m ) O(Qm) O(Qm)
转移方程: f i , j = p f i , j + 1 + q f i , j f_{i,j}=pf_{i,j+1}+qf_{i,j} fi,j=pfi,j+1+qfi,j,注意 f i , 0 = p f i , 1 + f i , 0 f_{i,0}=pf_{i,1}+f_{i,0} fi,0=pfi,1+fi,0


对于第二个技能,想要知道命中 u u u的概率,我们需要知道除了u之外还有 j j j个人存活下,不妨叫做 g u , j g_{u,j} gu,j

只需要把除了 u u u的其他敌人 v v v拿出来跑一遍背包即可(注意逆序)
g u , j = alive v × g u , j − 1 + dead v × g u , j g_{u,j}=\text{alive}_v×g_{u,j-1}+\text{dead}_v×g_{u,j} gu,j=alivev×gu,j1+deadv×gu,j

显然 alive v = 1 − f v , 0 \text{alive}_v=1-f_{v,0} alivev=1fv,0:v存活下来的概率, dead v = f v , 0 \text{dead}_v=f_{v,0} deadv=fv,0死了的概率。

对于第二个技能范围的每个敌人,我们都需要预处理一下 g u , j g_{u,j} gu,j数组,也就是 O ( n 3 ) O(n^3) O(n3)的复杂度,第二个技能总时间复杂度 O ( C n 3 ) O(Cn^3) O(Cn3),代码如下这时候我们能够拿到 70 70 70pts, O ( Q m + C n 3 ) O(Qm+Cn^3) O(Qm+Cn3)

#include<cstring>
#include<iostream>
using namespace std;
using ll=long long;
constexpr ll mod=998244353;
ll qmi(ll a,ll b)
{
    ll res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
ll inv[205],a[205],b[205];
ll f[205][105];//f[i][j]第i个人还有j滴血的概率
ll g[205];//将u那一维压缩了
int n,m;
void attack(int i,ll p)//O(qc)
{
    ll q=(1-p+mod)%mod;
    for(int j=0;j<=a[i];j++)
    {
        if(j)
            f[i][j]=(p*f[i][j+1]%mod+q*f[i][j]%mod)%mod;
        else
            f[i][j]=(p*f[i][j+1]%mod+f[i][j])%mod;
    }
}
void solve(int k)//O(n^3)
{
    for(int u=1;u<=k;u++)//枚举范围呢的每一个敌人
    {
        memset(g,0,sizeof g);g[0]=1ll;
        for(int i=1;i<=k;i++)//除了u的敌人跑一边背包
        {
            if(u==i) continue;
            
            ll alive=((1ll-f[b[i]][0])%mod+mod)%mod;
            ll dead=((1ll-alive)%mod+mod)%mod;
            for(int j=i;j>=0;j--)//逆序!
            {
                if(j) 
                    g[j]=(g[j]*dead+g[j-1]*alive%mod)%mod;
                else 
                    g[j]=g[j]*dead%mod;
            }
                
        }
        ll ans=0;
        for(int j=0;j<k;j++) ans=(ans+g[j]*inv[j+1]%mod)%mod;
        ans=ans*((1ll-f[b[u]][0])%mod+mod)%mod;
        cout<<ans<<' ';
    }
    cout<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        inv[i]=qmi(i,mod-2);
        f[i][a[i]]=1;
    }
    cin>>m;
    while(m--)
    {
        int op;
        cin>>op;
        if(op==0)
        {
            int id;ll u,v;
            cin>>id>>u>>v;
            attack(id,1ll*u*qmi(v,mod-2)%mod);
        }
        else
        {
            int k;
            cin>>k;
            for(int i=1;i<=k;i++) cin>>b[i];
            solve(k);
        }
        
    }
    
    for(int i=1;i<=n;i++)
    {
        ll ans=0;
        for(int j=1;j<=a[i];j++)
            ans=(ans+j*f[i][j]%mod)%mod;
        cout<<ans<<' ';
    }
    return 0;
}

考虑优化,显然我们TLE是由于第二个操作,如果每次枚举然后跑背包似乎有些冗余。我们另设 g j g_j gj表示 j j j个人或者的概率,而用 h u , j h_{u,j} hu,j表示除了u之外还有 j j j个人存活下(也就是上面的 g u , j g_{u,j} gu,j)有下面递推
g j = alive u × h u , j − 1 + dead u × h u , j g_j=\text{alive}_u×h_{u,j-1}+\text{dead}_u×h_{u,j} gj=aliveu×hu,j1+deadu×hu,j
于是有 h u , j = g j − alive u × h u , j − 1 dead u h_{u,j}=\frac{g_j-\text{alive}_u×h_{u,j-1}}{\text{dead}_u} hu,j=deadugjaliveu×hu,j1
于是我们只需要 O ( n 2 ) O(n^2) O(n2)预处理 g j g_j gj,然后枚举 u u u线性求出 h u , j h_{u,j} hu,j同样时间复杂度 O ( n 2 ) O(n^2) O(n2),那么技能二时间复杂度 O ( C n 2 ) O(Cn^2) O(Cn2)

注意 g j = alive u × h u , j − 1 , dead u = 0 g_j=\text{alive}_u×h_{u,j-1},\text{dead}_u=0 gj=aliveu×hu,j1,deadu=0
即存在 h u , j = g j + 1 h_{u,j}=g_{j+1} hu,j=gj+1

总时间复杂度 O ( Q m + C n 2 ) O(Qm+Cn^2) O(Qm+Cn2)

#include<cstring>
#include<iostream>
using namespace std;
using ll=long long;
constexpr ll mod=998244353;
ll qmi(ll a,ll b)
{
    ll res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
ll inv[205],a[205],b[205];
ll f[205][105];
ll g[205],h[205];//h同样可以少一维
int n,m;
void attack(int i,ll p)//O(qc)
{
    ll q=(1-p+mod)%mod;
    for(int j=0;j<=a[i];j++)
    {
        if(j)
            f[i][j]=(p*f[i][j+1]%mod+q*f[i][j]%mod)%mod;
        else
            f[i][j]=(p*f[i][j+1]%mod+f[i][j])%mod;
    }
}
void solve(int k)
{
    memset(g,0,sizeof g);g[0]=1ll;
    for(int i=1;i<=k;i++)//预处理 g
    {
        ll alive=((1ll-f[b[i]][0])%mod+mod)%mod;
        ll dead=((1ll-alive)%mod+mod)%mod;
        for(int j=i;j>=0;j--)
        {
            if(j) 
                g[j]=(g[j]*dead+g[j-1]*alive%mod)%mod;
            else 
                g[j]=g[j]*dead%mod;
        }
            
    }
    
    for(int u=1;u<=k;u++)
    {
        ll ans=0;
        ll alive=((1ll-f[b[u]][0])%mod+mod)%mod;
        ll dead=((1ll-alive)%mod+mod)%mod;
        memset(h,0,sizeof h);
        if(alive!=1)//1-dead != 0
        {
            ll invd=qmi(dead,mod-2);
            h[0]=g[0]*invd%mod;
            for(int j=1;j<k;j++)
                h[j]=(g[j]-alive*h[j-1]%mod+mod)%mod*invd%mod;
        }
        else//dead=1
        {
            for(int j=0;j<=k;j++)
                h[j]=g[j+1];
        }
        for(int j=0;j<k;j++) ans=(ans+h[j]*inv[j+1]%mod)%mod;
        ans=ans*alive%mod;
        cout<<ans<<' ';
    }
    cout<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        inv[i]=qmi(i,mod-2);
        f[i][a[i]]=1;
    }
    cin>>m;
    while(m--)
    {
        int op;
        cin>>op;
        if(op==0)
        {
            int id;ll u,v;
            cin>>id>>u>>v;
            attack(id,1ll*u*qmi(v,mod-2)%mod);
        }
        else
        {
            int k;
            cin>>k;
            for(int i=1;i<=k;i++) cin>>b[i];
            solve(k);
        }
    }
    
    for(int i=1;i<=n;i++)
    {
        ll ans=0;
        for(int j=1;j<=a[i];j++)
            ans=(ans+j*f[i][j]%mod)%mod;
        cout<<ans<<' ';
    }
    return 0;
}

要加油哦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值