嘘~ 正在从服务器偷取页面 . . .

UOJ340 【清华集训2017】小 Y 和恐怖的奴隶主 题解


UOJ340 【清华集训2017】小 Y 和恐怖的奴隶主 题解

题目链接:#340. 【清华集训2017】小 Y 和恐怖的奴隶主

题意

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

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

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

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

输入格式

从标准输入读入数据。

输入第一行包含三个正整数 $T, m, k$,$T$ 表示询问组数,$m, k$ 的含义见题目描述。

接下来 $T$ 行,每行包含一个正整数 $n$,表示询问进行 $n$ 次攻击后扣减 Boss 的生命值点数的期望。

输出格式

输出共 $T$ 行,对于每个询问输出一行一个非负整数,表示该询问的答案对 $998244353$ 取模的结果。

可以证明,所求期望一定是一个有理数,设其为 $\frac{p}{q}$($\mathrm{gcd}(p,q) = 1$),那么你输出的数 $x$ 要满足 $p \equiv qx \pmod{998244353}$。

数据范围

$1 \le T \le 1000,~1 \le n \le 10^{18},~m = 1,2,3,~1\le k \le 8$

据说概率要顺推,期望要逆推?

容易想到是矩阵快速幂优化期望dp。

注意到 $m$ 很小,且 $k$ 也很小,所以我们直接枚举每个血量各有多少人。

设 $f_{i,a,b,c}$ 表示前 $i$ 次攻击后,场上有 $a$ 个 $1$ 点血,$b$ 个 $2$ 点血,$c$ 个 $3$ 点血的期望。

转移很简单,就是乘一乘概率系数啥的,这里就略去了。或者可以看参考文献[2]的分析

注意到 $f_i$ 只有 $165 + 1$ 个状态,其中一个是额外加上的,表示 boss 扣血的期望 $g$ 。


关于构建矩阵,估计也有很多人和我一样知道矩阵快速幂,但不会代码实现矩阵的构建。

最开始学斐波那契数列的矩阵快速幂方法时,只是看这个矩阵挺好用,怎么正好就推出来下一个。

现在做了这道题,感觉对这个矩阵的理解又加深了一些。

对于转移矩阵 $M$ ,考察 $M_{i,j}$ 的数学意义。

状态数组(行向量)左乘 $M_{i,j}$ 其实就是:「原向量第 $i$ 个状态」转移到「新向量第 $j$ 个状态」时乘的系数

也可以说,「新向量第 $j$ 个状态」由「原向量第 $i$ 个状态」转移过来时乘的系数。

举个例子,斐波那契数列

因为有递推式 $f_1=1,~f_2=1,~f_i = f_{i-1} + f_{i-2}$ 。

本质上矩阵就是多个行向量构成的,因此对于矩阵乘法,我们也可以用这样的方式理解,如

属于是复习一下矩阵快速幂了(


然而直接矩阵快速幂是 $\mathcal{O}(T166^3\log n)$ 的,会 TLE。

考虑预处理 $2^k$ 次幂的矩阵,这样查询就可以少掉一个 $M\times M$ 的第 $k$ 维枚举了。

时间复杂度 $\mathcal{O}(T166^2\log n)$

代码:(这题卡常,可以用 __int128 优化取模操作)

#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
#pragma GCC optimize("Ofast,unroll-loops,fast-math")
typedef long long ll;
const ll mod = 998244353;
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
typedef vector< vector<int> > mat;
#define N ((int)(166 + 15))
#define K 11

mat M[70],ans;
int n,Q,m,k,cnt,inv[10],id[K][K][K];
int qpow(int a,int b)
{
    int ans = 1, base = a % mod;
    for(; b; b >>= 1)
    {
        if(b & 1) ans = 1ll * ans * base % mod;
        base = 1ll * base * base % mod;
    }
    return ans;
}
mat operator * (mat &A, mat &B)
{
    int n = A.size() - 1, m = A[0].size() - 1, p = B[0].size() - 1;
    mat C(n + 1, vector<int>(p + 1));
    for(int i=1; i<=n; i++)
        for(int j=1; j<=p; j++)
        {
            __int128 t = 0;
            for(int k=1; k<=m; k++) t += 1ll * A[i][k] * B[k][j];
            C[i][j] = t % mod;
    }
    return C;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> Q >> m >> k; M[0].resize(N, vector<int>(N));
    for(int i=1; i<=9; i++) inv[i] = qpow(i, mod - 2);
    for(int a=0; a<=k; a++)
        for(int b=0; b <= (m >= 2 ? k - a : 0); b++)
            for(int c=0; c <= (m == 3 ? k - a - b : 0); c++) id[a][b][c] = ++cnt;
    ++cnt; M[0][cnt][cnt] = 1; ans.resize(2, vector<int>(cnt + 1));
    for(int a=0; a<=k; a++)
        for(int b=0; b <= (m >= 2 ? k - a : 0); b++)
            for(int c=0; c <= (m == 3 ? k - a - b : 0); c++)
            {
                int i = id[a][b][c], iv = inv[a + b + c + 1], d = a + b + c < k;
                switch(m)
                {
                    case 1:
                        if(a) M[0][i][id[a - 1][b][c]] = 1ll * a * iv % mod;
                        break;
                    case 2:
                        if(a) M[0][i][id[a - 1][b][c]] = 1ll * a * iv % mod;
                        if(b) M[0][i][id[a + 1][b - 1 + d][c]] = 1ll * b * iv % mod;
                        break;
                    case 3:
                        if(a) M[0][i][id[a - 1][b][c]] = 1ll * a * iv % mod;
                        if(b) M[0][i][id[a + 1][b - 1][c + d]] = 1ll * b * iv % mod;
                        if(c) M[0][i][id[a][b + 1][c - 1 + d]] = 1ll * c * iv % mod;
                        break;
                }
                M[0][i][i] = M[0][i][cnt] = iv;
            }
    for(int i=1; i<=60; i++) M[i] = M[i-1] * M[i-1];
    for(ll n; Q; Q--)
    {
        cin >> n;
        for(int i=1; i<=cnt; i++) ans[1][i] = 0;
        ans[1][id[m == 1][m == 2][m == 3]] = 1;
        for(int i=0; i<=60; i++) if((n >> i) & 1) ans = ans * M[i];
        cout << ans[1][cnt] << '\n';
    }
    return 0;
}

参考文献

[1] https://m-sea.blog.luogu.org/solution-p4007

[2] https://www.luogu.com.cn/blog/Mrsrz/solution-p4007


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录