top1编程
← 返回题目
题解

游戏

1 条题解

  • 0
    @ 2026-8-7 16:01:05

    P4893 游戏(提高)

    解题思路

    第一步,读懂题目。 你有四个正整数 n、a、b、c。每轮操作可以从 n 里减去 a,或者减去 b,一直减到 n≤c 为止。问一共有多少种不同的操作序列,答案对 1e9+7 取模。注意:就算 a=b,减去 a 和减去 b 也算两种不同的操作。

    第二步,用动态规划数方案。 设 dp[i] 表示"当前数是 i 时,还能产生多少种不同的结束序列"。如果 i≤c,游戏已经结束,什么都不用做,只有 1 种序列,所以 dp[i]=1。如果 i>c,那么这一轮要么减 a 要么减 b,所以 dp[i]=dp[i-a]+dp[i-b]。

    第三步,注意操作必须合法。 题目里减完的数不能是负数,也就是说只有当 i≥a 时才能执行"减 a",只有当 i≥b 时才能执行"减 b"。如果一个数 i>c 但 i<a 且 i<b,它既不能减 a 也不能减 b,走不下去了,这种状态 dp[i]=0,不算合法的结束序列。

    具体例子: 样例 n=1,a=1,b=1,c=1,一开始 n≤c,游戏直接结束,答案就是 1。再看 n=5,a=1,b=1,c=1:每一步可以从两个方向各来一次,相当于每一步都有 2 种选择,dp[2]=2×dp[1]=2,dp[3]=4,dp[4]=8,dp[5]=16。

    边界情况: n 最大是 200000,所以 dp 数组要开到 200005。两个 dp 值相加可能很大,记得每一步都对 1e9+7 取模;为了安全先转成 long long 再加。

    参考代码

    // 游戏:n每轮减a或减b,直到n<=c,求不同操作序列数对1e9+7取模
    #include <iostream>
    using namespace std;
    
    const int MOD = 1000000007;
    int n, a, b, c;
    int dp[200005]; // dp[i]表示当前数i能产生的序列数
    
    int main() {
        cin >> n >> a >> b >> c;
        for (int i = 0; i <= c && i <= n; i++)
            dp[i] = 1; // 已经<=c,只有"不再操作"这一种序列
        for (int i = c + 1; i <= n; i++) {
            long long s = 0;
            // 只有i>=a才允许减a(结果不能为负), 减完<=c则序列记1
            if (i >= a) s += (i - a <= c) ? 1 : dp[i - a];
            // 减b同理; a与b相等也算不同操作, 所以两种都要加
            if (i >= b) s += (i - b <= c) ? 1 : dp[i - b];
            dp[i] = (int)(s % MOD);
        }
        cout << dp[n] << endl;
        return 0;
    }
    

    复杂度分析

    只需要从 c+1 一直算到 n,每个数 O(1) 算一次,所以时间复杂度是 O(n),n 最大 200000,很快。空间上用一个长度约 n 的数组,是 O(n)。

    • 1