LOJ2325「清华集训 2017」小Y和恐怖的奴隶主(期望概率+矩阵快速幂)

本文解析了一道名为“小Y和恐怖的奴隶主”的游戏策略问题,通过动态规划方法计算攻击Boss的生命值点数的期望。文章详细介绍了如何将多维DP数组转化为矩阵快速幂的形式,并提供了优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LOJ2325「清华集训 2017」小Y和恐怖的奴隶主

原题地址https://loj.ac/problem/2325

题意:

"A fight? Count me in!" 要打架了,算我一个。

"Everyone, get in here!" 所有人,都过来!

小Y是一个喜欢玩游戏的OIer。一天,她正在玩一款游戏,要打一个Boss。

虽然这个Boss有 10100 10 100 ​​点生命值,但它只带了一个随从——一个只有 m m 点生命值的“恐怖的奴隶主”。

这个“恐怖的奴隶主”有一个特殊的技能:每当它被扣减生命值但没有死亡(死亡即生命值 0),且Boss的随从数量小于上限 k k ,便会召唤一个新的具有 m点生命值的“恐怖的奴隶主”。

现在小Y可以进行 n n 次攻击,每次攻击时,会从Boss以及Boss的所有随从中的等概率随机选择一个,并扣减1 点生命值,她想知道进行 n n 次攻击后扣减Boss的生命值点数的期望。为了避免精度误差,你的答案需要对998244353取模。

数据范围
1T1000,1n1018,1m3,1k8 1 ≤ T ≤ 1000 , 1 ≤ n ≤ 10 18 , 1 ≤ m ≤ 3 , 1 ≤ k ≤ 8

题解:

最朴素的DP,利用概率算期望,记录第i轮,分别有j,k,l个血量为1,2,3的奴隶主的概率。
DP[i+1][j][k][l]+=k/j/lj+k+l+1DP[i][j][k][l] D P [ i + 1 ] [ j ′ ] [ k ′ ] [ l ′ ] + = k / j / l j + k + l + 1 D P [ i ] [ j ] [ k ] [ l ]
每个状态都有 1j+k+l+1 1 j + k + l + 1 攻击boss,有 1j+k+l+1dp[i][j][k][l] 1 j + k + l + 1 ∗ d p [ i ] [ j ] [ k ] [ l ] 的贡献。

n太大了,考虑矩阵快速幂。
如何把后面三维压成一维呢?
发现所有合法状态最多 C210+C29+...+C22=165 C 10 2 + C 9 2 + . . . + C 2 2 = 165 个,
可以直接把所有状态之间的转移关系列出来,然后跑矩阵快速幂。
但是,复杂度为 O(tot3logn) O ( t o t 3 l o g n ) ,要T。

复杂度的瓶颈在于T组数据,每次矩阵*矩阵都是 tot3 t o t 3 ,
但其实我们只需要答案那个 1×tot 1 × t o t 的数组。
那么,预处理出2的幂次方的矩阵,由于矩阵乘法的结合率,
Ans=IniMaxn=IniMax(101...0)2=IniMax2sMax2s1...Max20 A n s = I n i ∗ M a x n = I n i ∗ M a x ( 101...0 ) 2 = I n i ∗ M a x 2 s ∗ M a x 2 s − 1 ∗ . . . M a x 2 0
一项一项,每次都是Ans向量乘矩阵,复杂度 O(tot3logn+Ttot2logn) O ( t o t 3 l o g n + T ∗ t o t 2 l o g n )

学习:

  • 对于和的期望不好求,可以算每个状态的概率*它的贡献。(不过逆推可以较容易地求得和的期望)
  • 表示清晰的多维DP数组难以矩阵快速幂,但是可以看看所有状态有多少,把状态间的关系列出来,就可以转移。
  • 对于在贡献在中间统计的情况,例如这个 1j+k+l+1dp[i][j][k][l] 1 j + k + l + 1 ∗ d p [ i ] [ j ] [ k ] [ l ] 的处理,矩阵多开一位当计数器即可,既继承上一个,又加上新增的。
  • 当然这道题可以逆推,即 dp[i][j][k][l] d p [ i ] [ j ] [ k ] [ l ] 表示从第i轮到n轮,期望的攻击boss次数。 DP[i1][j][k][l]+=k/j/lj+k+l+1(DP[i][j][k][l]+w) D P [ i − 1 ] [ j ′ ] [ k ′ ] [ l ′ ] + = k / j / l j + k + l + 1 ( D P [ i ] [ j ] [ k ] [ l ] + w ) ,攻击boss时w为1,否则为0。
    顺推: now[1][i]=pre[1][j]M[j][i] n o w [ 1 ] [ i ] = ∑ p r e [ 1 ] [ j ] ∗ M [ j ] [ i ]
    倒推: now[i][1]=M[i][j]pre[j][1] n o w [ i ] [ 1 ] = ∑ M [ i ] [ j ] ∗ p r e [ j ] [ 1 ]

优化:

  • 预处理
  • 利用好矩阵的运算律。
  • 少模一点。可以设置一个很大的lim,超了就减回来,最后再模。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define LL unsigned long long
using namespace std;
const int mod=998244353;
const LL lim=16940360401038606353llu;
const int N=10;
const int MXN=170;
int T,m,K,tot=0,id[N][N][N];
LL n,inv[N],ans[170],tmp[MXN];
struct Matrix
{
    LL M[MXN][MXN];
    void init() {memset(M,0,sizeof(M));}
    Matrix operator*(const Matrix &A)
    {
        Matrix ret; ret.init();
        for(int i=1;i<=tot+1;i++)
        for(int j=1;j<=tot+1;j++)
        {
            for(int k=1;k<=tot+1;k++)
            {
                ret.M[i][j]+=M[i][k]*A.M[k][j];
                if(ret.M[i][j]>=lim) ret.M[i][j]-=lim;
            }
            ret.M[i][j]%=mod;
        }
        return ret;
    }
}f[65];
inline LL modpow(LL A,LL B)
{
    LL ret=1; LL base=A;
    for(;B;B>>=1) 
    {
        if(B&1) ret=ret*base%mod; 
        base=base*base%mod;
    }
    return ret;
}
void muti(Matrix &A)
{
    memset(tmp,0,sizeof(tmp));
    for(int i=1;i<=tot+1;i++)  //tmp[1][i]=ans[1][j]*M[j][i]
    {
        for(int j=1;j<=tot+1;j++)
        {
            tmp[i]+=ans[j]*A.M[j][i];
            if(tmp[i]>=lim) tmp[i]-=lim;
        }
        tmp[i]%=mod;
    }
    memcpy(ans,tmp,sizeof(tmp));
}
int main()
{
    scanf("%d%d%d",&T,&m,&K);
    for(int i=0;i<=K;i++)
    for(int j=0;j<=((m>1)?K-i:0);j++)
    for(int k=0;k<=((m>2)?K-i-j:0);k++)
    id[i][j][k]=++tot;
    for(int i=0;i<=K+1;i++) inv[i]=modpow(i,mod-2);
    for(int i=0;i<=63;i++) f[i].init();
    for(int i=0;i<=K;i++)
    for(int j=0;j<=((m>1)?K-i:0);j++)
    for(int k=0;k<=((m>2)?K-i-j:0);k++)
    {
        int cur=id[i][j][k],nk=(i+j+k)<K; LL iv=inv[i+j+k+1];
        if(m==1) f[0].M[cur][id[i-1][j][k]]=iv*i%mod;
        if(m==2)
        {
            if(i) f[0].M[cur][id[i-1][j][k]]=iv*i%mod;
            if(j) f[0].M[cur][id[i+1][j-1+nk][k]]=iv*j%mod;
        }
        if(m==3)
        {
            if(i) f[0].M[cur][id[i-1][j][k]]=iv*i%mod;
            if(j) f[0].M[cur][id[i+1][j-1][k+nk]]=iv*j%mod;
            if(k) f[0].M[cur][id[i][j+1][k-1+nk]]=iv*k%mod;
        }
        f[0].M[cur][cur]=f[0].M[cur][tot+1]=iv;
    }
    f[0].M[tot+1][tot+1]=1;
    for(int i=1;i<=63;i++) f[i]=f[i-1]*f[i-1];
    while(T--) 
    {
        scanf("%lld",&n);
        memset(ans,0,sizeof(ans));
        if(m==1) ans[id[1][0][0]]=1;
        else if(m==2) ans[id[0][1][0]]=1;
        else if(m==3) ans[id[0][0][1]]=1;
        for(int i=0;n;n>>=1,i++) if(n&1) muti(f[i]);
        printf("%lld\n",ans[tot+1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值