题解:洛谷 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

【核心思想】

  1. 问题分析:给定初始值 \(n\),每轮可选择减去 \(a\) 或减去 \(b\),直到 \(n \leq c\) 时结束。求不同的操作序列总数,对 \(10^9+7\) 取模。这是一个线性动态规划问题,核心在于将"从 \(n\)\(\leq c\) 的路径数"转化为状态转移方程。

  2. 算法选择

    • 一维线性 DP:设 \(f[i]\) 表示从初始值 \(i\) 开始到达终止状态的方案数
    • 逆序递推:从 \(n\) 向下递推到 \(c+1\),每个状态向 \(i-a\)\(i-b\) 转移
    • 偏移量处理:由于 \(i-a\)\(i-b\) 可能为负,使用数组偏移 \(N\) 避免负数下标
  3. 关键步骤

    • 读入数据:读取 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n - c)\),从 \(n\) 递推到 \(c+1\)
    • 空间复杂度:\(O(n + N)\)\(N\) 为偏移量,\(N = 2 \times 10^5\)
  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
posted @ 2026-07-17 15:47  团爸讲算法  阅读(6)  评论(0)    收藏  举报