[GESP202312 六级] 闯关游戏

发布于:2025-02-23 ⋅ 阅读:(12) ⋅ 点赞:(0)

题目描述

你来到了一个闯关游戏。

这个游戏总共有 N N N 关,每关都有 M M M 个通道,你需要选择一个通道并通往后续关卡。其中,第 i i i 个通道可以让你前进 a i a_i ai 关,也就是说,如果你现在在第 x x x 关,那么选择第 i i i 个通道后,你将直接来到第 x + a i x+a_i x+ai 关(特别地,如果 x + a i ≥ N x + a_i \geq N x+aiN,那么你就通关了)。此外,当你顺利离开第 s s s 关时,你还将获得 b s b_s bs 分。

游戏开始时,你在第 0 0 0 关。请问,你通关时最多能获得多少总分。

输入格式

第一行两个整数 N N N M M M,分别表示关卡数量和每关的通道数量。

接下来一行 M M M 个用单个空格隔开的整数 a 0 , a 1 ⋯   , a M − 1 a_0,a_1\cdots,a_{M-1} a0,a1,aM1。保证 1 ≤ a i ≤ N 1\le a_i \le N 1aiN

接下来一行 N N N 个用单个空格隔开的整数 b 0 , b 1 ⋯   , b N − 1 b_0,b_1\cdots,b_{N-1} b0,b1,bN1。保证 ∣ b i ∣ ≤ 1 0 5 |b_i|\le 10^5 bi105

输出格式

一行一个整数,表示你通关时最多能够获得的分数。

输入输出样例 #1

输入 #1

6 2 
2 3
1 0 30 100 30 30

输出 #1

131

输入输出样例 #2

输入 #2

6 2
2 3
1 0 30 100 30 -1

输出 #2

101

说明/提示

样例解释 1

你可以在第 0 0 0 关选择第 1 1 1 个通道,获得 1 1 1 分并来到第 3 3 3 关;随后再选择第 0 0 0 个通道,获得 100 100 100 分并来到第 5 5 5 关;最后任选一个通道,都可以获得 30 30 30 分并通关。如此,总得分为 1 + 100 + 30 = 131 1+100+30=131 1+100+30=131

样例解释 2

请注意,一些关卡的得分可能是负数。

数据范围

对于 20 % 20\% 20% 的测试点,保证 M = 1 M=1 M=1

对于 40 % 40\% 40% 的测试点,保证 N ≤ 20 N \le 20 N20;保证 M ≤ 2 M\le 2 M2

对于所有测试点,保证 1 ≤ N ≤ 1 0 4 1 \le N \le 10^4 1N104;保证 1 ≤ M ≤ 100 1 \le M\le 100 1M100

提交链接

闯关游戏

解析

搜索的想法(40分)

深度优先搜索(DFS) 来枚举所有可能的路径,以找到通关时的最高得分。

主要逻辑

  1. dfs(pos, score)
  • pos 代表当前关卡编号
  • score 代表目前的累计得分
  • 递归终止条件:当 pos >= n(即通关)时,更新 mx 记录的最大得分。
  • 否则,尝试 m 种通道,每种都递归调用 dfs(pos + a[i], score + b[pos])
  1. main()
  • 读取输入 N N N, M M M(关卡数和通道数)。
  • 读取每个通道的跳跃距离 a [ i ] a[i] a[i]
  • 读取每一关的得分 b [ i ] b[i] b[i]
  • 调用 dfs(0, 0)0 关开始搜索所有可能的通关路径。

时间复杂度分析

最坏情况分析:在最坏情况下,DFS 会搜索所有可能的通关路径。

状态树的深度

  • 最深可能达到 N N N(最坏情况下一次只前进 1 1 1 关)。

每层的分支数

  • 每一层最多有 M M M 个选择(每一关有 M M M 个通道)。

搜索空间

  • 递归构成了一棵 最大深度 N N N,每个节点最多 M M M 个子节点 的搜索树。
  • 这样搜索树的节点数最多是 M N M^N MN(指数级别)。

对于 40 % 40\% 40% 的测试点,保证 N ≤ 20 N \le 20 N20;保证 M ≤ 2 M\le 2 M2,时间复杂度可以承受。

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e4 + 9;

int n, m, a[109], b[MAX_N], mx = -1e9;  //mx初值为一个比较小的数(分数有负数存在)

int cost[10009];

void dfs(int pos, int score)
{
    if (pos >= n)
    {
        mx = max(mx, score);
        return;
    }
    for (int i = 0; i < m; i++)
        dfs(pos + a[i], score + b[pos]);
}
int main()
{
    cin >> n >> m; // 关卡的数量  每一关的通道数量
    for (int i = 0; i < m; i++)
        cin >> a[i]; // 第i个通道前进a[i]关
    for (int i = 0; i < n; i++)
        cin >> b[i]; // 通过第i关时获得的分数为b[i]
    dfs(0, 0);
    cout << mx;
    return 0;
}

动态规划的想法(100分)

动态规划(DP) 来代替深度优先搜索。动态规划能避免递归树的指数级别增长,优化到 O ( N ∗ M ) O(N*M) O(NM) 的复杂度。

使用一个一维 d p dp dp 数组来表示从第 x x x 关卡到达时的最大得分。然后通过迭代的方式,更新每个关卡的得分。

动态规划思路:

状态定义:

  • d p [ x ] dp[x] dp[x] 表示到达 x x x 关时的最大得分。

状态转移:

  • 从关卡 x x x 选择任意通道 i i i,到达 x + a [ i ] x + a[i] x+a[i] 关,得分更新为 d p [ x + a [ i ] ] = m a x ( d p [ x + a [ i ] ] , d p [ x ] + b [ x ] ) dp[x + a[i]] = max(dp[x + a[i]], dp[x] + b[x]) dp[x+a[i]]=max(dp[x+a[i]],dp[x]+b[x])

初始化:

  • d p [ 0 ] = 0 dp[0] =0 dp[0]=0(从第 0 0 0 关开始)。

目标:

  • 最终我们希望到达的关卡 ≥ n ≥n n,表示最后能通关时的最高得分。每一个 a [ i ] a[i] a[i] 最大为 n n n,我们可以求 d p [ n ] ∼ d p [ 2 ∗ n ] dp[n] \sim dp[2*n] dp[n]dp[2n] 的最大值即可。

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e4 + 9;

int n, m, a[109], b[MAX_N], mx = -1e9;
int dp[2 * MAX_N]; // dp[i]:到达第i个关卡的最大分数

int main()
{
    cin >> n >> m; // 关卡的数量  每一关的通道数量
    for (int i = 1; i <= 2 * n; i++)
        dp[i] = -1e9;

    for (int i = 0; i < m; i++)
        cin >> a[i]; // 第i个通道前进a[i]关
    for (int i = 0; i < n; i++)
        cin >> b[i]; // 通过第i关时获得的分数为b[i]

    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            dp[i + a[j]] = max(dp[i + a[j]], dp[i] + b[i]);

    for (int i = n; i <= 2 * n; i++)
        mx = max(mx, dp[i]);
    cout << mx;
    return 0;
}