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

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\) 个状态」转移过来时乘的系数。

举个例子,斐波那契数列 \[ \begin{bmatrix} f_i&f_{i-1} \end{bmatrix} \times \begin{bmatrix} 1&1\\1&0 \end{bmatrix} = \begin{bmatrix} f_{i+1}&f_{i} \end{bmatrix} \] 因为有递推式 \(f_1=1,~f_2=1,~f_i = f_{i-1} + f_{i-2}\)

本质上矩阵就是多个行向量构成的,因此对于矩阵乘法,我们也可以用这样的方式理解,如 \[ \begin{bmatrix}1&2\\3&4\end{bmatrix} \times \begin{bmatrix}5&6\\7&8\end{bmatrix} = \begin{bmatrix}5 \times 1 + 7 \times 2 & 6 \times 1+ 8 \times 2 \\ 5 \times 3 + 7 \times 4&6 \times 3 + 8 \times 4\end{bmatrix} = \begin{bmatrix}19&22\\43&50\end{bmatrix} \] 属于是复习一下矩阵快速幂了(


然而直接矩阵快速幂是 \(\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 !
评论
  目录