拿金币 蓝桥杯

image-20220125181841695


解题思路

这是一道典型的动态规划题,我们需要从上往下、从左往右遍历每一个元素,比较当前元素的左边元素和上边元素的大小(无则视为0),然后将较大元素值与当前元素值相加赋给当前元素,即此操作后当前元素为走到当前位置可以拿到的最大金币数量,遍历完每一元素后即可得到答案。

代码

#include <iostream>
using namespace std;
int map[1005][1005] = {0};
int main()
{
    int n = 0;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> map[i][j];
            if (map[i - 1][j] > map[i][j - 1])
            {
                map[i][j] += map[i - 1][j];
            }
            else
            {
                map[i][j] += map[i][j - 1];
            }
        }
    }
    cout << map[n][n] << endl;
    return 0;
}
<think>我们参考引用[1]和引用[4]:问题描述是从一个N x N的方格的最左上角出发,每次只能向右或向下移动,目标是到达右下角,并收集尽可能多的金币。 这是一个经典的动态规划问题。我们可以用一个二维数组dp[i][j]表示从起点(0,0)走到位置(i,j)所能获得的最大金币数。 状态转移方程:因为每次只能向右或向下移动,所以到达(i,j)的位置只能从(i-1,j)或(i,j-1)过来(注意边界条件)。 因此:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j] 初始条件:dp[0][0] = grid[0][0] 注意:对于第一行和第一列需要单独处理: 第一行:只能从左边过来,所以dp[0][j] = dp[0][j-1] + grid[0][j] 第一列:只能从上边过来,所以dp[i][0] = dp[i-1][0] + grid[i][0] 但是,引用[4]中给出了一个更简洁的写法,它使用了一维数组进行动态规划(滚动数组优化): for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) { scanf("%d", &nums[j]); // 注意这里读入的是当前行的数据 dp[j] = max(dp[j], dp[j - 1]) + nums[j]; } 解释:这里dp数组在每一行开始时,dp[j]保存的是上一行第j列的最大金币数(即从上一行j列来的路径),而dp[j-1]保存的是当前行第j-1列的最大金币数(即从左来的路径)。 因此,dp[j] = max(上一行同一列的值,当前行前一列的值) + 当前格子的金币数。 注意:由于我们只关心上一行和当前行,所以可以用一维数组来优化空间。 我们按照引用[1]的样例输入: 3 1 3 3 2 2 2 3 1 2 按照上述动态规划过程,我们逐步计算: 初始化:dp数组长度为n+1(索引从1开始),初始全0。 第一行:j从1到3 j=1: dp[1] = max(dp[1], dp[0]) + 1 -> max(0,0)+1=1 -> dp[1]=1 j=2: dp[2] = max(dp[2], dp[1]) + 3 -> max(0,1)+3=4 -> dp[2]=4 j=3: dp[3] = max(dp[3], dp[2]) + 3 -> max(0,4)+3=7 -> dp[3]=7 第二行:重新输入第二行数据:2 2 2 j=1: dp[1] = max(dp[1](上一行的dp[1]=1), dp[0]=0) + 2 -> max(1,0)+2=3 -> dp[1]=3 j=2: dp[2] = max(dp[2](上一行的dp[2]=4), dp[1]=3) + 2 -> max(4,3)+2=6 -> dp[2]=6 j=3: dp[3] = max(dp[3](上一行的dp[3]=7), dp[2]=6) + 2 -> max(7,6)+2=9 -> dp[3]=9 第三行:输入第三行数据:3 1 2 j=1: dp[1] = max(dp[1](上一行的dp[1]=3), dp[0]=0) + 3 -> max(3,0)+3=6 -> dp[1]=6 j=2: dp[2] = max(dp[2](上一行的dp[2]=6), dp[1]=6) + 1 -> max(6,6)+1=7 -> dp[2]=7 j=3: dp[3] = max(dp[3](上一行的dp[3]=9), dp[2]=7) + 2 -> max(9,7)+2=11 -> dp[3]=11 输出dp[n]即dp[3]=11,与样例输出一致。 因此,我们可以按照这个思路编写代码。 注意:题目中n<=1000,所以用二维数组会占用较大内存(1000*1000=1e6个整数,在C++中是可以接受的,但用一维数组优化空间更优)。 下面我们给出两种方法的代码: 方法一:二维动态规划 步骤: 1. 读入n 2. 创建一个n*n的二维数组grid,存储每个格子的金币数 3. 创建一个n*n的二维数组dp,或者直接使用grid原地更新(但通常不破坏原数组) 4. 初始化dp[0][0]=grid[0][0] 5. 初始化第一行和第一列 6. 从第1行到第n-1行,第1列到第n-1列(如果从0开始索引,则行和列从1开始)进行状态转移 7. 输出dp[n-1][n-1](如果索引从0开始)或dp[n][n](如果索引从1开始) 方法二:一维动态规划(滚动数组) 步骤: 1. 读入n 2. 创建一个大小为n+1的一维数组dp(索引0~n),初始化为0 3. 循环i从1到n(行): 循环j从1到n(列): 读入当前格子的金币数(或者先读入一行再处理,这里代码是边读边处理) dp[j] = max(dp[j], dp[j-1]) + 当前金币数 4. 输出dp[n] 注意:在方法二中,我们按行处理,每行处理前dp数组保存的是上一行的结果。由于我们每行只处理一次,所以可以边读边处理。 根据引用[4]的代码,我们注意到它使用了一维数组,并且读入是放在内层循环中,即每读一个数就处理一个数。 但是,引用[4]的代码中有一个细节:它使用了一个nums数组来存储当前行的每个元素,但实际上我们可以直接用一个临时变量,或者直接覆盖dp数组(因为dp[j]在更新前保存的是上一行的信息,更新后就是当前行的信息,而下一行需要的是当前行的信息)。所以直接用一个一维数组dp即可。 但是,引用[4]的代码中,每次读入一个数到nums[j](注意j从1开始),然后更新dp[j] = max(dp[j], dp[j-1]) + nums[j]。这里dp[j]在更新前保存的是上一行第j列的值,dp[j-1]保存的是当前行第j-1列的值(因为在这一行中,dp[j-1]已经被更新过了)。 因此,我们按照引用[4]的代码实现。 注意:由于题目要求左上角到右下角,且只能向右或向下,所以上述动态规划是适用的。 下面我们给出代码(方法二,一维数组优化):</think>### 蓝桥杯 C++ 金币数量问题求解 #### 问题描述 在一个 $N \times N$ 的方格中,每个格子有若干金币。从左上角 $(0,0)$ 出发,每次只能向右或向下移动,求到达右下角 $(N-1,N-1)$ 时能获得的最大金币数量。 #### 动态规划解法(空间优化) 使用一维数组 `dp` 进行状态压缩,空间复杂度 $O(N)$: ```cpp #include <iostream> #include <algorithm> using namespace std; const int MAX_N = 1010; int dp[MAX_N]; // dp[j] 表示到达当前行第 j 列的最大金币数 int main() { int n; cin >> n; // 初始化第一行 for (int j = 0; j < n; j++) { cin >> dp[j]; if (j > 0) dp[j] += dp[j - 1]; // 只能从左向右 } // 处理后续行 for (int i = 1; i < n; i++) { int current; cin >> current; dp[0] += current; // 第一列只能从上向下 for (int j = 1; j < n; j++) { cin >> current; // 状态转移:max(从上向下, 从左向右) dp[j] = max(dp[j], dp[j - 1]) + current; } } cout << dp[n - 1] << endl; return 0; } ``` #### 核心算法分析 1. **状态定义** $dp[j]$ 表示到达当前行第 $j$ 列的最大金币数。 2. **状态转移方程** $$ dp[j] = \max(dp[j]_{\text{上一行}}, dp[j-1]_{\text{当前行}}) + \text{grid}[i][j] $$ - $dp[j]$(更新前):上一行同列的最大值(表示向下移动) - $dp[j-1]$:当前行前一列的最大值(表示向右移动) 3. **边界处理** - 第一行:只能向右移动 $dp[j] = dp[j-1] + \text{grid}[0][j]$ - 第一列:只能向下移动 $dp[0] = dp[0] + \text{grid}[i][0]$ 4. **时间复杂度** $O(N^2)$,遍历 $N \times N$ 网格一次。 #### 样例验证 输入: ``` 3 1 3 3 2 2 2 3 1 2 ``` 计算过程: 1. 第一行:$dp = [1, 4, 7]$ 2. 第二行: - $j=0$:$dp[0] = 1 + 2 = 3$ - $j=1$:$dp[1] = \max(4, 3) + 2 = 6$ - $j=2$:$dp[2] = \max(7, 6) + 2 = 9$ 3. 第三行: - $j=0$:$dp[0] = 3 + 3 = 6$ - $j=1$:$dp[1] = \max(6, 6) + 1 = 7$ - $j=2$:$dp[2] = \max(9, 7) + 2 = 11$ 输出:`11`(符合要求) #### 关键优化点 - **滚动数组**:复用一维数组避免二维数组的空间开销($N \leq 1000$ 时节省约 4MB 内存)。 - **边读入边计算**:无需额外存储整个网格,降低空间复杂度。 #### 注意事项 1. 输入数据时需按行顺序处理。 2. 数组索引从 0 开始,注意边界条件。 3. 对于 $N=1$ 的特例,程序直接输出第一个元素。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值