abc275_e Sugoroku 4 题解

发布时间 2023-05-31 17:17:29作者: luogu_wsy0704

Sugoroku 4

题意

有一行格子,编号为 \(0, 1, \cdots n\),你站在 \(0\) 号格子上。

你手上有一个转盘,转盘上写有数字 \(1 \sim m\),每次转转盘后显示的数值是等概率出现的。

假设某次转转盘后转盘显示的数值为 \(x\),你会行走 \(x\) 步,当你走到第 \(n + 1\) 个格子时,你会回头行走剩余步数,形同飞行棋的最后几步。

当你在某次走完后恰好停在第 \(n + 1\) 个格子上时游戏结束,你最多转 \(k\) 次转盘,问胜利的概率对 \(998244353\) 取模的结果。

数据范围

  • \(1 \leqslant n, k \leqslant 1000\)
  • \(1 \leqslant m \leqslant \min(n, 10)\)

思路

一个明显的概率 dp。

提到等概率和取模,不难想到费马小定理,转盘上显示某个数的概率为 \(\frac{1}{m}\),根据费马小定理可得 \(\frac{1}{m} \equiv \frac{1}{m} \times m ^ {998244353 - 1} \equiv m ^ {998244353 - 2} \equiv m ^{998244351} \pmod{998244353}\),可以用快速幂求,下面用 \(qp\) 表示。

定义 \(F_{i,j}\) 表示 \(i\) 是否可以转一次转盘走到 \(j\),如果是则为 \(1\),否则为 \(0\)

  • 状态:\(dp_{i, j}\) 表示转了 \(i\) 次转盘走到编号为 \(j\) 的格子的概率。
  • 转移:\(dp_{i, j} = \sum\limits_{0 \leqslant k < n} F_{k, i} \times dp_{i - 1, k} \times qp\),你也可以思考一下扩散性转移。
  • 初始状态:\(dp_{0, 0} = 1\)
  • 目标状态:\(\sum\limits_{1 \leqslant i \leqslant k} dp_{i, n}\)

注意取模和乘法爆 int 的细节即可。

复杂度

  • 时间:\(O(n \times m \times k)\)
  • 空间:\(O(n \times k)\)

Code

点击查看代码
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int mod = 998244353, N = 1e3 + 10;

int n, m, k;
ll qp, dp[N][N], ans;

ll qmod (ll a, int b) { // 快速幂求 qp
  ll ans = 1;
  while (b) {
    if (b & 1) {
      ans = (ans * a) % mod;
    }
    a = (a * a) % mod, b >>= 1;
  }
  return ans;
}

int Q (int x, int t) { // 求按规则从 x 往后走 t 步会走到的格子的编号
  if (x + t <= n) { // 不用回头
    return x + t;
  }
  return 2 * n - t - x; // 回头
}

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m >> k;
  qp = qmod(m, mod - 2), dp[0][0] = 1;
  for (int i = 0; i < k; i++) { // 扩散形转移好写
    for (int j = 0; j < n; j++) {
      for (int l = 1; l <= m; l++) { // 枚举走多少步
        dp[i + 1][Q(j, l)] = (dp[i + 1][Q(j, l)] + dp[i][j] * qp) % mod; // 取模!
      }
    }
  }
  for (int i = 1; i <= k; i++) {
    ans = (ans + dp[i][n]) % mod;
  }
  cout << ans;
  return 0;
}