1 条题解
-
0
#include <bits/stdc++.h> #define ll long long #define R register #define mk(x,y) make_pair(x,y) #define PII pair<int,int> using namespace std; const int N = 2005, mod = 1e9 + 7, inv2 = (mod + 1 >> 1); inline ll qmul(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans = ans * a % mod; a = (a * a) % mod; b >>= 1; } return ans; } inline ll inv(int x) { return qmul(x, mod - 2); } struct Vector { ll k, b; } a[N]; int n, k; ll f[N][N]; inline int find(int x, int all) { return ((x - 1) % all + all) % all + 1; } int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> k; f[1][1] = 1; for (R int i = 2; i <= n; ++i) { int tk = find(k, i); int t = __gcd(i, tk), wei; for (R int rst = 1; rst <= t; ++rst) { ll K = 1, B = 0, wei = find(rst + tk, i); do { if (wei == tk) a[wei] = {K *inv2 % mod, B *inv2 % mod}; else a[wei] = {K *inv2 % mod, (f[i - 1][find(wei - tk, i)] + B) *inv2 % mod}; K = a[wei].k, B = a[wei].b; wei = find(wei + tk, i); } while (wei != find(rst + tk, i)); ll x = (mod - B * inv(K - 1) % mod) % mod; f[i][rst] = x; wei = find(rst + tk, i); while (wei != rst) { f[i][wei] = (a[wei].k * x % mod + a[wei].b) % mod; wei = find(wei + tk, i); } } } cout << f[n][1]; return 0; }约瑟夫环存活概率问题题解
题目分析
n 个人逆时针排成环,从编号 1 开始逆时针报数,报到 k 的人有 概率出局。无论是否出局,下一个人从 1 开始继续报数。求编号 1 最后存活的概率,结果对 取模。
核心难点:
- 可达 ,无法模拟报数过程;
- 存活概率涉及无穷级数(如样例),需通过数学转化为有限方程求解;
- 环结构的周期性与状态转移的依赖关系,需高效优化。
算法标签
- 动态规划(DP)
- 约瑟夫环问题
- 数论(gcd、逆元、快速幂)
- 线性方程组求解
- 循环分块优化
动态规划模型
状态定义
设 表示 i 个人围成环时,编号 j 的人最后存活的概率。
- 边界条件:(1 个人时必然存活);
- 目标答案:(n 个人时编号 1 的存活概率)。
关键推导与优化
1. 报数目标位置的周期性
由于环长为 i,报数到 k 的位置具有周期性,可简化为:
即 i 人环中第一次报数的目标位置(后续报数目标位置按环周期重复)。
2. 环的循环分块(gcd 优化)
每次报数跳 tk 步,在 i 人环中会形成 t 个独立循环,其中 。
- 每个循环的长度为 ,循环间互不影响,可独立求解;
- 分循环处理后,时间复杂度从 降至 (n≤2000 可承受)。
3. 状态转移方程(线性关系推导)
对于 i 人环中的位置 j,第一次报数目标为 tk,分两种情况推导转移关系:
情况 1:j == tk(当前位置是报数目标)
- 有 概率出局,贡献 0;
- 有 概率留下,后续存活概率依赖下一个报数周期的状态(即循环中下一个位置的 )。
- 转移关系:$f[i][j] = \frac{1}{2} \cdot 0 + \frac{1}{2} \cdot f[i][next_j]$,整理为线性形式: 其中 ( 对 的逆元), 是循环中下一个位置。
情况 2:j != tk(当前位置不是报数目标)
- 有 概率 tk 出局,进入 i-1 人环,j 对应的 i-1 人环位置为 (位置映射),贡献 ;
- 有 概率 tk 留下,后续存活概率依赖循环中下一个位置的 。
- 转移关系:$f[i][j] = \frac{1}{2} \cdot f[i-1][pos] + \frac{1}{2} \cdot f[i][next_j]$,整理为线性形式:$$f[i][j] = inv2 \cdot f[i][next_j] + (inv2 \cdot f[i-1][pos]) $$
4. 线性方程组求解
每个循环中的所有位置 可表示为线性关系:(x 为循环中基准位置的概率值)。
- 遍历循环建立所有线性表达式,形成闭环方程 ;
- 求解基准值:(逆元处理模运算);
- 回代所有线性表达式,得到循环中所有位置的 。
代码解析
核心函数与变量
变量/函数 功能描述 DP 状态:i 人环中位置 j 的存活概率 i 人环中报数目标位置 环的独立循环数 的逆元( 模意义下) 快速幂求 x 的逆元(基于费马小定理) 计算 x 在 all 个元素的环中的合法位置(避免越界) Vector 存储线性系数 ,即 关键流程
- 初始化:(边界条件)。
- 迭代人数 i 从 2 到 n:
- 计算 i 人环的报数目标位置 ;
- 分循环处理(按 划分循环);
- 每个循环内建立线性表达式,求解基准值并回代得到 。
- 输出答案:。
代码片段详解
线性表达式建立(循环内)
do { if (wei == tk) a[wei] = {K * inv2 % mod, B * inv2 % mod}; else a[wei] = {K * inv2 % mod, (f[i - 1][find(wei - tk, i)] + B) * inv2 % mod}; K = a[wei].k, B = a[wei].b; wei = find(wei + tk, i); } while (wei != find(rst + tk, i));- 遍历循环中每个位置,根据是否为报数目标,建立线性系数 ;
- 和 迭代更新,传递线性关系至下一个位置。
基准值求解与回代
ll x = (mod - B * inv(K - 1) % mod) % mod; f[i][rst] = x; wei = find(rst + tk, i); while (wei != rst) { f[i][wei] = (a[wei].k * x % mod + a[wei].b) % mod; wei = find(wei + tk, i); }- 闭环方程求解基准值 (模意义下处理负数);
- 用 回代所有线性表达式,得到循环内所有位置的 。
时间复杂度分析
- 对于每个 i(2≤i≤n),环分为 个循环,总处理次数为 ;
- 每个操作(gcd、快速幂、线性表达式建立与回代)均为 ;
- 整体时间复杂度:,适配 的数据范围。
空间复杂度分析
- DP 数组 占用 空间(n≤2000,约 400 万存储单元,可接受);
- 辅助数组(线性系数 、循环栈等)占用 空间;
- 整体空间复杂度:。
- 1
信息
- ID
- 5377
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者