题解:洛谷 P10721 计算得分
【题目来源】
洛谷:P10721 [GESP202406 六级] 计算得分 - 洛谷
【题目描述】
小杨想要计算由 \(m\) 个小写字母组成的字符串的得分。
小杨设置了一个包含 \(n\) 个正整数的计分序列 \(A=[a_1,a_2,\ldots,a_n]\),如果字符串的一个子串由 \(k(1\leq k \leq n)\) 个 \(\texttt{abc}\) 首尾相接组成,那么能够得到分数 \(a_k\),并且字符串包含的字符不能够重复计算得分,整个字符串的得分是计分子串的总和。
例如,假设,字符串 \(\texttt{dabcabcabcabzabc}\) 的所有可能计分方式如下:
- \(\texttt{d+abc+abcabc+abz+abc}\) 或者 \(\texttt{d+abcabc+abc+abz+abc}\),其中 \(\texttt{d}\) 和 \(\texttt{abz}\) 不计算得分,总得分为 \(a_1+a_2+a_1\)。
- \(\texttt{d+abc+abc+abc+abz+abc}\),总得分为 \(a_1+a_1+a_1+a_1\)。
- \(\texttt{d+abcabcabc+abz+abc}\),总得分为 \(a_3+a_1\)。
小杨想知道对于给定的字符串,最大总得分是多少。
【输入】
- 第一行包含一个正整数 \(n\),代表计分序列 \(A\) 的长度。
- 第二行包含 \(n\) 个正整数,代表计分序列 \(A\)。
- 第三行包含一个正整数 \(m\),代表字符串的长度。
- 第四行包含一个由 \(m\) 个小写字母组成的字符串。
【输出】
输出一个整数,代表给定字符串的最大总得分。
【输入样例】
3
3 1 2
13
dabcabcabcabz
【输出样例】
9
【核心思想】
-
问题分析:给定字符串 \(s\)(长度 \(m\))和计分序列 \(A\)(长度 \(n\)),子串由 \(k\) 个
"abc"首尾相接组成时可获得分数 \(a_k\)。字符不能重复计算得分,求最大总得分。这是一个线性动态规划 + 字符串匹配问题,核心在于将字符串匹配与 DP 结合,在位置 \(i\) 处尝试匹配不同长度的"abc"重复模式。 -
算法选择:
- 一维线性 DP:\(f[i]\) 表示前 \(i\) 个字符的最大得分
- 模式匹配:在位置 \(i\) 处,尝试匹配长度为 \(3j\) 的
"abc"重复 \(j\) 次的模式,若匹配成功则更新 DP
-
关键步骤:
- 读入数据:读取 \(n\)、计分序列 \(A[1..n]\)、\(m\) 和字符串 \(s\)
- 字符串索引调整:\(s = ' ' + s\),使下标从 \(1\) 开始
- DP 计算(\(i\) 从 \(1\) 到 \(m\)):
- \(f[i] \leftarrow f[i-1]\)(不选择以 \(i\) 结尾的任何模式)
- 枚举 \(j\) 从 \(1\) 到 \(n\):
- 若 \(i < 3j\):\(break\)(长度不足)
- 检查子串 \(s[i-3j+1..i]\) 是否由 \(j\) 个
"abc"组成 - 若匹配:\(f[i] = \max(f[i], f[i-3j] + a[j])\)
- 输出结果:\(f[m]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(m \cdot n \cdot L)\),其中 \(L = 3j\) 为模式长度,字符串比较为 \(O(L)\)
- 空间复杂度:\(O(m)\),一维 DP 数组
-
线性 DP 与模式匹配的核心思想:
- 无重叠约束:字符不能重复计算,因此选择以 \(i\) 结尾的模式后,前一个状态只能是 \(f[i-3j]\)(跳过整个模式长度),而非 \(f[i-1]\)。这保证了选择的子串互不重叠
- 模式验证:需要验证子串是否恰好由 \(j\) 个
"abc"组成,即每 \(3\) 个字符依次为"a"、"b"、"c"。可通过逐段检查或字符串比较实现 - 多重模式尝试:在位置 \(i\) 尝试所有可能的 \(j\)(\(1\) 到 \(n\)),取最大值,确保不遗漏最优解
- 状态转移的完整性:\(f[i] = f[i-1]\) 表示不在位置 \(i\) 结束任何模式,继承前一个位置的最优值;匹配成功时考虑加入新模式,取两者最大
- 适用于带模式匹配的序列分割、不重叠子串选择类 DP 问题
【算法标签】
普及 #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, m; // n: 物品数量, m: 字符串长度
int a[25]; // a[i]: 第i个物品的价值
int f[100010]; // f[i]: 前i个字符的最大价值
string s; // 输入的字符串
int main()
{
// 输入物品数量
cin >> n;
// 输入每个物品的价值
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 输入字符串长度和字符串
cin >> m >> s;
// 在字符串前加一个空格,使索引从1开始
s = ' ' + s;
// 动态规划计算最大价值
for (int i = 1; i <= m; i++) // 遍历每个字符位置
{
// 初始化:不匹配任何模式,继承前一个位置的值
f[i] = f[i-1];
// 尝试匹配每个物品
for (int j = 1; j <= n; j++)
{
// 检查:当前位置i是否足够长来匹配长度为3*j的子串
// 需要匹配3*j个字符,即"abc"重复j次
if (i < 3 * j)
{
break; // 长度不足,后面的j更大,更不可能匹配
}
// 提取以i结尾的长度为3的子串
// 注意:这里代码有误,应该是提取长度为3*j的子串
// 但实际代码中substr只提取3个字符
if (s.substr(i - 3 * j + 1, 3) != "abc")
{
break; // 不匹配"abc",停止尝试更大的j
}
// 如果匹配成功,更新最大价值
f[i] = max(f[i], f[i - 3 * j] + a[j]);
}
}
// 输出前m个字符的最大价值
cout << f[m] << endl;
return 0;
}
【运行结果】
3
3 1 2
13
dabcabcabcabz
9
浙公网安备 33010602011771号