[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主

试题描述

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

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

小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\) 取模的结果。

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

输入示例

3 2 6
1
2
3

输出示例

499122177
415935148
471393168

数据规模及约定

在所有测试点中,\(1 \leq T \leq 1000, 1 \leq n \leq {10}^{18}, 1 \leq m \leq 3, 1 \leq k \leq 8\)。

题解

搜一下发现状态数最多只有 \(164\) 个,那就矩阵快速幂求每种状态 \(n\) 次攻击后的概率,期望每步累加即可。

这真是道神尼玛辣鸡卡常题啊!

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
#define LL long long

LL read() {
    LL x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
    return x * f;
}

#define maxn 15
#define maxs 167
#define maxq 1010
#define MOD 998244353
#define maxlog 60

int cnts, m, K;
vector <int> sta[maxs];
map <vector <int>, int> id;

void dfs(vector <int> A) {
    if(id.count(A)) return ;
    sta[id[A] = ++cnts] = A;
    int p = K;
    rep(i, 0, K - 1) if(!A[i]){ p = i; break; }
    rep(i, 0, p - 1) {
        vector <int> B = A;
        B[i]--;
        if(B[i] && p <= K - 1) B[p] = m;
        sort(B.begin(), B.end());
        rep(j, 0, K - 1 >> 1) swap(B[j], B[K-1-j]);
        dfs(B);
    }
    return ;
}

int inv[maxn];

LL buf[maxs];
struct Matrix {
    int n, m, A[maxs][maxs];
    Matrix() {}
    Matrix(int _, int __): n(_), m(__) {}
    Matrix operator * (const Matrix& t) const {
        Matrix ans(n, t.m);
        rep(i, 1, ans.n) {
            memset(buf, 0, sizeof(buf));
            const int *a = A[i];
            rep(k, 1, m) {
                const int *ta = t.A[k];
                LL tmp = *(a + k);
                rep(j, 1, ans.m) buf[j] += tmp * *(ta + j) % MOD;
            }
            rep(j, 1, ans.m) ans.A[i][j] = buf[j] % MOD;
        }
        return ans;
    }
    Matrix operator = (const Matrix& t) {
        n = t.n; m = t.m;
        rep(i, 1, cnts + 1) memcpy(A[i], t.A[i], sizeof(t.A[i]));
        return *this;
    }
    void print() {
        printf("Matrix(%d, %d)\n", n, m);
        rep(i, 1, n) rep(j, 1, m) printf("%d%c", A[i][j], j < m ? ‘ ‘ : ‘\n‘);
        return ;
    }
} tr, mpow[maxlog];
int Ans[maxs];
LL Que[maxq];

int main() {
    int T = read(); m = read(); K = read();

    vector <int> A(K);
    rep(i, 0, K - 1) A[i] = 0; A[0] = m;
    dfs(A);
//  printf("cnts: %d\n", cnts);
    /*rep(i, 1, cnts) {
        printf("sta[%d]: ", i);
        rep(j, 0, K - 1) printf("%d%c", sta[i][j], j < K - 1 ? ‘ ‘ : ‘\n‘);
    } // */

    inv[1] = 1;
    rep(i, 2, K + 1) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;

    tr = Matrix(cnts + 1, cnts + 1);
    rep(s, 1, cnts) {
        A = sta[s];
        int p = K;
        rep(i, 0, K - 1) if(!A[i]){ p = i; break; }
        rep(i, 0, p - 1) {
            vector <int> B = A;
            B[i]--;
            if(B[i] && p <= K - 1) B[p] = m;
            sort(B.begin(), B.end());
            rep(j, 0, K - 1 >> 1) swap(B[j], B[K-1-j]);
            (tr.A[s][id[B]] += inv[p+1]) %= MOD;
        }
        tr.A[s][s] += inv[p+1];
        if(tr.A[s][s] >= MOD) tr.A[s][s] -= MOD;
        tr.A[s][cnts+1] += inv[p+1];
        if(tr.A[s][cnts+1] >= MOD) tr.A[s][cnts+1] -= MOD;
    }
    tr.A[cnts+1][cnts+1] = 1;

    LL mx = 0;
    rep(q, 1, T) Que[q] = read(), mx = max(mx, Que[q]);
    mpow[0] = tr;
    mx >>= 1;
    for(int i = 1; mx; mx >>= 1, i++) mpow[i] = mpow[i-1] * mpow[i-1];

    rep(kase, 1, T) {
        LL n = Que[kase];

        memset(Ans, 0, sizeof(Ans)); Ans[1] = 1;
        int t = 0;
        while(n) {
            if(n & 1) {
                memset(buf, 0, sizeof(buf));
                rep(k, 1, cnts + 1) {
                    const int *a = mpow[t].A[k];
                    LL tmp = Ans[k];
                    rep(j, 1, cnts + 1) buf[j] += tmp * *(a + j) % MOD;
                }
                rep(i, 1, cnts + 1) Ans[i] = buf[i] % MOD;
            }
            t++; n >>= 1;
        }
        printf("%d\n", Ans[cnts+1]);
    }

    return 0;
}
时间: 2024-11-05 06:23:24

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主的相关文章

LibreOJ #2325. 「清华集训 2017」小Y和恐怖的奴隶主(矩阵快速幂优化DP)

哇这题剧毒,卡了好久常数才过T_T 设$f(i,s)$为到第$i$轮攻击,怪物状态为$s$时对boss的期望伤害,$sum$为状态$s$所表示的怪物个数,得到朴素的DP方程$f(i,s)=\sum \frac{1}{sum+1}*(f(i+1,s')+[s==s'])$ 状态数只有$C_{8+3}^3=165$个,所以就可以矩乘优化啦.再加上一个用于转移的$1$,矩阵大小是$166*166$的,因为多组询问,所以可以先把$2$的所有次幂的矩阵都预处理出来. 然后会发现复杂度是$O(T*166^3

[LOJ#2327]「清华集训 2017」福若格斯

[LOJ#2327]「清华集训 2017」福若格斯 试题描述 小d是4xx9小游戏高手. 有一天,小d发现了一个很经典的小游戏:跳青蛙. 游戏在一个 \(5\) 个格子的棋盘上进行.在游戏的一开始,最左边的两个格子上各有一个向右的青蛙,最右边的两个格子上各有一个向左的青蛙. 每次移动可以选取一个青蛙,向这只青蛙的前方移动一格到空格子中或跳过前方的一个不同朝向的青蛙并移动到空格子中. 为了使你更好地理解这个游戏,我们下发了一个游戏demo作为参考(注意:这个demo中的棋盘大小和题目中并不相同).

[LOJ#2328]「清华集训 2017」避难所

[LOJ#2328]「清华集训 2017」避难所 试题描述 "B君啊,你当年的伙伴都不在北京了,为什么你还在北京呢?" "大概是因为出了一些事故吧,否则这道题就不叫避难所了." "唔,那你之后会去哪呢?" "去一个没有冬天的地方." 对于一个正整数 \(n\),我们定义他在 \(b\) 进制下,各个位上的数的乘积为 \(p = F(n, b)\). 比如 \(F(3338, 10) = 216\). 考虑这样一个问题,已知 \

[LOJ#2331]「清华集训 2017」某位歌姬的故事

[LOJ#2331]「清华集训 2017」某位歌姬的故事 试题描述 IA是一名会唱歌的女孩子. IOI2018就要来了,IA决定给参赛选手们写一首歌,以表达美好的祝愿.这首歌一共有 \(n\) 个音符,第iii个音符的音高为 \(h_i\).IA的音域是 \(A\),她只能唱出 \(1\sim A\) 中的正整数音高.因此 \(1\le h_i\le A\). 在写歌之前,IA需要确定下这首歌的结构,于是她写下了 \(Q\) 条限制,其中第 \(i\) 条为:编号在 \(l_i\) 到 \(r_

Loj #6703 -「清华集训 2017」生成树计数

\[ \sum _{\sum v_i = n-2} \prod (a_i ^{v_i+1} * (v_i+1) ^ m /v_i!) *(\sum (v_i+1)^m) \] 将 \(\sum (v_i+1)^m\) 中的贡献分开算. 我们有两个生成函数. 第一个: \[ \sum _i a^{i+1} * (v_i+1)^m x^i / i! \] 第二个: \[ \sum _i a^{i+1} * (i+1)^{2m} x^i / i! \] 先将 \(\prod a\) 算到前面. 令:

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

loj2322 「清华集训 2017」Hello world!

https://loj.ac/problem/2322 先吐槽一下,sb数据毁我青春败我前程. 首先,一个数开根开不了多少次. 当我们把它开到1的时候,我们以后就不需要开他了,我们可以利用并查集跳过他,这是套路. 但是这个每次走$k$步,让人很头痛. 于是乎……分块 首先,对于$k$比较大的情况,我们可以暴力跳.否则, 我们对于每个$k$建一棵树,第$i$棵树上$x$的父亲是原树上$x$的第$i$个祖先 然后树剖,用线段树或者树状数组维护一下就好了. 然后!!打开榜,rk1,啥子,并查集+暴力跳

「清华集训 2017」无限之环

无限之WA https://www.luogu.org/problemnew/show/P4003 本题如果知道是网络流的话,其实建图不算特别神奇,但是比较麻烦. 数据范围过大,插头dp不能处理,而且是一个网格图,考虑网络流. 先看是不是二分图? 每个格子只会和相邻四个格子发生关系 所以,黑白染色正好. i+j为偶数左部点,i+j为奇数右部点 不漏水是什么? 每个管道的四个口都能和别的接好. S向左,右向T连口数的容量,费用为0的边 考虑怎么能把左右连在一起. 考虑要上下左右四个方向匹配,那么必

「清华集训 2017」某位歌姬的故事

题目链接 问题分析 吐槽一下这个预处理比DP还长的题-- 首先对限制从小到大排序,然后不难发现对于每一种大小限制都是独立的.离散后考虑\(F[i][j]\)表示以\(i\)结尾,上一个音高为限制大小的位置\(j\)的方案种数.不难发现对于一些右端点相同的限制,左端点最右的限制才有效.这样就可以\(n^2\)动规了. 由于要离散化,所以细节很多. 参考程序 程序没有显式的离散化,并且大量使用结构体,所以又慢又长. #include <bits/stdc++.h> #define LL long