题解:洛谷 P17015 消消乐
【题目来源】
洛谷:P17015 [GESP202606 七级] 消消乐 - 洛谷
【题目描述】
给定一个由 \(n\) 个整数构成的数组 \(a = [a_1, \ldots, a_n]\)。每次你可以对数组 \(a\) 进行以下操作,直到数组 \(a\) 变为空:
- 指定 \(a\) 中的一个元素,获得该元素两侧相邻元素之和的分数,并将该元素从 \(a\) 中删去。
特别地,如果相邻元素不存在则该元素的值视为 \(0\)。例如,对于 \(a = [1, 2, 3]\) 可以进行以下操作:
- 指定元素 \(2\),获得分数 \(1 + 3\),删去 \(2\) 后 \(a = [1, 3]\);
- 指定元素 \(1\),获得分数 \(0 + 3\),删去 \(1\) 后 \(a = [3]\);
- 指定元素 \(3\),获得分数 \(0 + 0\),删去 \(3\) 后 \(a\) 变为空。
请问你能获得的分数总和最大是多少?
【输入】
第一行,一个正整数 \(n\),表示数组长度。
第二行,\(n\) 个非负整数 \(a_1, \ldots, a_n\),表示数组 \(a\) 中的整数。
【输出】
输出一行,一个整数,表示能获得的最大分数总和。
【输入样例】
6
1 6 3 2 9 1
【输出样例】
55
【核心思想】
-
问题分析:给定数组 \(a_1, a_2, \ldots, a_n\),每次删除一个元素获得其两侧相邻元素之和的分数,求删除所有元素的最大分数总和。这是一个区间动态规划问题,核心在于分析删除顺序对得分的影响——最后删除的元素在得分时其相邻元素是固定的区间外边界。
-
算法选择:
- 区间 DP:设 \(dp[i][j]\) 为删除区间 \([i, j]\) 内所有元素能获得的最大分数
- 最后删除策略:枚举区间 \([i, j]\) 内最后一个被删除的元素 \(k\),此时 \(k\) 的相邻元素为 \(a_{i-1}\) 和 \(a_{j+1}\)(区间外的边界元素),得分固定
-
关键步骤:
- 边界处理:\(a_0 = 0\),\(a_{n+1} = 0\),作为虚拟边界
- 初始化(区间长度为 \(1\)):\(dp[i][i] = a_{i-1} + a_{i+1}\),删除单个元素 \(i\) 时得分为其两侧相邻元素之和
- 区间 DP(\(len\) 从 \(2\) 到 \(n\)):
- 枚举左端点 \(i\),计算右端点 \(j = i + len - 1\)
- 枚举最后删除的元素 \(k \in [i, j]\):
- \(dp[i][j] = \max(dp[i][j], dp[i][k-1] + dp[k+1][j] + a_{i-1} + a_{j+1})\)
- 解释:先分别删除 \([i, k-1]\) 和 \([k+1, j]\) 内的元素,最后删除 \(k\),此时 \(k\) 的相邻元素为 \(a_{i-1}\) 和 \(a_{j+1}\)
- 输出结果:\(dp[1][n]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n^3)\),三层循环:区间长度 \(O(n)\)、左端点 \(O(n)\)、枚举最后删除元素 \(O(n)\)
- 空间复杂度:\(O(n^2)\),二维 \(dp\) 数组
-
区间动态规划的核心思想:
- 最后删除的确定性:对于区间 \([i, j]\),无论内部删除顺序如何,最后一个被删除的元素 \(k\) 在得分时,其相邻元素一定是 \(a_{i-1}\) 和 \(a_{j+1}\)。这是因为 \([i, j]\) 内其他元素已被删除,\(k\) 的左右邻居就是区间边界外的元素
- 子问题独立性:删除 \([i, k-1]\) 和 \([k+1, j]\) 是两个独立的子问题,互不影响,满足最优子结构
- 区间长度递增:按区间长度从小到大枚举,确保计算 \(dp[i][j]\) 时,所有子区间 \(dp[i][k-1]\) 和 \(dp[k+1][j]\) 已经计算完成
- 边界元素的固定贡献:最后删除 \(k\) 的得分 \(a_{i-1} + a_{j+1}\) 与 \(k\) 的具体值无关,只与区间边界有关,这是状态转移的关键
- 适用于元素删除顺序影响得分、且最后操作的相邻元素具有确定性特征的区间优化类问题
【算法标签】
普及 #区间DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 105; // 常量:最大数组长度
int n; // n: 数组长度
int a[N]; // a[i]: 数组元素,a[0] 和 a[n+1] 视为边界(值为0)
int dp[N][N]; // dp[i][j]: 删除区间 [i,j] 内所有元素能获得的最大分数
signed main()
{
cin >> n; // 读入数组长度
for (int i = 1; i <= n; i++) // 读入数组元素
cin >> a[i];
for (int i = 1; i <= n; i++) // 初始化:区间长度为 1 的情况
dp[i][i] = a[i - 1] + a[i + 1]; // 删除单个元素 i,得分 = 左侧相邻 + 右侧相邻
for (int len = 2; len <= n; len++) // 枚举区间长度,从 2 到 n
for (int i = 1; i + len - 1 <= n; i++) // 枚举区间左端点
{
int j = i + len - 1; // j: 区间右端点
for (int k = i; k <= j; k++) // 枚举区间内最后一个被删除的元素 k
{
// 状态转移:先删除 [i,k-1] 和 [k+1,j] 内的元素,最后删除 k
// 最后删除 k 时,其相邻元素为 a[i-1] 和 a[j+1](区间外的边界元素)
dp[i][j] = max(dp[i][j], dp[i][k - 1] + dp[k + 1][j] + a[i - 1] + a[j + 1]);
}
}
cout << dp[1][n] << endl; // 输出删除整个数组 [1,n] 的最大分数
return 0;
}
【运行结果】
6
1 6 3 2 9 1
55
浙公网安备 33010602011771号