题解:学而思编程 小猴拿糖
【题目来源】
学而思编程:小猴拿糖
【题目描述】
小猴面前有 \(n\) 个糖果排成一行,从左到右第 \(i\) 个糖的价值是 \(a_i\)。
小猴打算拿走一些糖,但是他不愿意拿走两个相邻的糖。
你要求出,在不同时拿走任意相邻的糖的前提下,小猴最多能拿走多少价值的糖?
【输入】
第一行一个整数 \(T\),表示有 \(T(1≤T≤50)\) 组数据。
接下来的每组数据,第一行是一个整数 \(N(1≤n≤10^5)\),表示一共有 \(n\) 个糖。
第二行是 \(n\) 个被空格分开的正整数,表示每个糖的价值,不超过 \(1000\)。
输入量较大,请使用scanf或者cin关闭流同步。
【输出】
对于每组数据,输出一行。
该行包括一个整数,表示在不同时拿走任意相邻的糖的前提下,最多能拿走的糖的价值。
【输入样例】
2
3
1 8 2
4
10 7 6 14
【输出样例】
8
24
【核心思想】
-
问题分析:给定 \(n\) 个糖果排成一行,每个糖果有价值 \(a_i\),要求选择若干糖果使得总价值最大,且不能选择相邻的糖果。这是一个经典的线性动态规划问题,类似于"打家劫舍"问题。
-
算法选择:
- 线性 DP(一维动态规划):定义状态表示每个位置选或不选的最大价值
- 状态设计:\(dp[i][0]\) 表示不选第 \(i\) 个糖果时的最大价值,\(dp[i][1]\) 表示选第 \(i\) 个糖果时的最大价值
-
关键步骤:
- 初始化:\(dp[0][0] = dp[0][1] = 0\),表示没有糖果时价值为 \(0\)
- 状态转移:
- 不选第 \(i\) 个:\(dp[i][0] = \max(dp[i-1][0], dp[i-1][1])\),可以从上一个选或不选转移过来
- 选第 \(i\) 个:\(dp[i][1] = dp[i-1][0] + a[i]\),只能从上一个不选转移过来(不能相邻)
- 答案:\(\max(dp[n][0], dp[n][1])\),取最后位置选或不选的最大值
-
时间/空间复杂度:
- 时间复杂度:\(O(T \cdot n)\),\(T\) 组数据,每组 \(O(n)\) 的 DP 计算
- 空间复杂度:\(O(n)\),DP 数组存储,可优化至 \(O(1)\)
-
线性 DP 的核心思想:
- 状态定义:明确每个位置的状态,本题用二维状态表示选/不选
- 状态转移:根据约束条件(不能相邻)设计转移方程
- 最优子结构:当前最优解由子问题的最优解组合而成
- 空间优化:由于只依赖前一行,可以用滚动数组将空间优化至 \(O(1)\)
- 适用于序列选择、路径规划等具有线性结构的问题
【算法标签】
线性DP-一维
【代码详解】
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 5;
int t, n, a[N], dp[N][2]; // dp[i][0]: 不选第i个, dp[i][1]: 选第i个
int main()
{
cin >> t;
while (t--)
{
memset(dp, 0, sizeof(dp)); // 重置DP数组
cin >> n;
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]); // 读取数组元素
}
for (int i = 1; i <= n; i++)
{
// 不选第i个元素
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
// 选第i个元素
dp[i][1] = dp[i - 1][0] + a[i];
}
cout << max(dp[n][0], dp[n][1]) << endl; // 输出最大和
}
return 0;
}
【运行结果】
2
3
1 8 2
8
4
10 7 6 14
24
浙公网安备 33010602011771号