题解:洛谷 P10376 游戏
【题目来源】
洛谷:P10376 [GESP202403 六级] 游戏 - 洛谷
【题目描述】
你有四个正整数 \(n,a,b,c\),并准备用它们玩一个简单的小游戏。
在一轮游戏操作中,你可以选择将 \(n\) 减去 \(a\),或是将 \(n\) 减去 \(b\)。游戏将会进行多轮操作,直到当 \(n \leq c\) 时游戏结束。
你想知道游戏结束时有多少种不同的游戏操作序列。两种游戏操作序列不同,当且仅当游戏操作轮数不同,或是某一轮游戏操作中,一种操作序列选择将 \(n\) 减去 \(a\),而另一种操作序列选择将 \(n\) 减去 \(b\)。如果 \(a=b\),也认为将 \(n\) 减去 \(a\) 与将 \(n\) 减去 \(b\) 是不同的操作。
由于答案可能很大,你只需要求出答案对 \(10^9 + 7\) 取模的结果。
【输入】
一行四个整数 \(n,a,b,c\)。
【输出】
输出一行一个整数表示答案。
【输入样例】
1 1 1 1
【输出样例】
1
【核心思想】
-
问题分析:给定初始值 \(n\),每轮可选择减去 \(a\) 或减去 \(b\),直到 \(n \leq c\) 时结束。求不同的操作序列总数,对 \(10^9+7\) 取模。这是一个线性动态规划问题,核心在于将"从 \(n\) 到 \(\leq c\) 的路径数"转化为状态转移方程。
-
算法选择:
- 一维线性 DP:设 \(f[i]\) 表示从初始值 \(i\) 开始到达终止状态的方案数
- 逆序递推:从 \(n\) 向下递推到 \(c+1\),每个状态向 \(i-a\) 和 \(i-b\) 转移
- 偏移量处理:由于 \(i-a\) 或 \(i-b\) 可能为负,使用数组偏移 \(N\) 避免负数下标
-
关键步骤:
- 读入数据:读取 \(n, a, b, c\)
- 初始化:\(f[n + N] = 1\)(从 \(n\) 开始有 \(1\) 种方案)
- 逆序 DP(\(i\) 从 \(n\) 到 \(c+1\)):
- \(f[i - a + N] = (f[i - a + N] + f[i + N]) \bmod MOD\)(选择减 \(a\))
- \(f[i - b + N] = (f[i - b + N] + f[i + N]) \bmod MOD\)(选择减 \(b\))
- 统计答案:\(ans = \sum_{i=0}^{c} f[i + N]\),即所有 \(\leq c\) 的终止状态的方案数之和
- 输出结果:\(ans \bmod MOD\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n - c)\),从 \(n\) 递推到 \(c+1\)
- 空间复杂度:\(O(n + N)\),\(N\) 为偏移量,\(N = 2 \times 10^5\)
-
线性 DP 与逆序递推的核心思想:
- 状态定义:\(f[i]\) 表示从 \(i\) 出发到达终止状态(\(\leq c\))的方案数。终止状态 \(i \leq c\) 时 \(f[i] = 1\)(已到达,无需操作),但本题中终止状态不直接参与递推,而是通过累加统计
- 逆序递推的正确性:从大到小递推确保计算 \(f[i]\) 时,\(f[i+a]\) 和 \(f[i+b]\) 已经计算完成(如果它们 \(> c\))。实际上本题是从 \(n\) 向下传播,每个状态向更小的状态转移
- 偏移量技巧:由于 \(i - a\) 或 \(i - b\) 可能为负数(当 \(i\) 较小时),使用 \(+N\) 偏移将所有下标平移到非负范围,避免数组越界
- 取模运算:每一步加法后取模,防止整数溢出,满足题目"答案对 \(10^9+7\) 取模"的要求
- 终止条件处理:\(i \leq c\) 时不再继续操作,因此这些状态作为"汇点"接收所有到达它们的方案。最终答案是所有 \(\leq c\) 状态的方案数之和
- 适用于路径计数、逆序递推、带偏移量的 DP 类问题
【算法标签】
普及- #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7; // 模数
const int N = 2e5 + 5; // 最大范围
int n, a, b, c, ans; // n: 初始值, a,b: 减少值, c: 目标范围上限, ans: 答案
int f[N << 1]; // DP数组,大小为2N,通过+N来避免负数下标
int main()
{
// 输入参数
cin >> n >> a >> b >> c;
// 初始化:从n开始有1种方案
f[n + N] = 1; // 为了防止下标为负数,统一加N偏移量
// 从n向下递推到c+1
for (int i = n; i > c; i--)
{
// 转移方程1:从i可以通过减a到达i-a
f[i - a + N] = (f[i - a + N] + f[i + N]) % mod; // f[i-a] += f[i]
// 转移方程2:从i可以通过减b到达i-b
f[i - b + N] = (f[i - b + N] + f[i + N]) % mod; // f[i-b] += f[i]
}
// 统计f[0]到f[c]的所有方案数之和
for (int i = 0; i <= N + c; i++) // 注意:这里应该是i <= c,但代码中有偏移
{
// 实际上,由于偏移N,f[i]对应原f[i-N]
// 循环中的i是实际下标,需要减去N
// 但代码直接累加f[i],这意味着i的范围是0到N+c
// 这似乎有问题
ans = (ans + f[i]) % mod;
}
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
114 51 4 1
176
浙公网安备 33010602011771号