题解:洛谷 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

【核心思想】

  1. 问题分析:给定字符串 \(s\)(长度 \(m\))和计分序列 \(A\)(长度 \(n\)),子串由 \(k\)"abc" 首尾相接组成时可获得分数 \(a_k\)。字符不能重复计算得分,求最大总得分。这是一个线性动态规划 + 字符串匹配问题,核心在于将字符串匹配与 DP 结合,在位置 \(i\) 处尝试匹配不同长度的 "abc" 重复模式。

  2. 算法选择

    • 一维线性 DP\(f[i]\) 表示前 \(i\) 个字符的最大得分
    • 模式匹配:在位置 \(i\) 处,尝试匹配长度为 \(3j\)"abc" 重复 \(j\) 次的模式,若匹配成功则更新 DP
  3. 关键步骤

    • 读入数据:读取 \(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]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \cdot n \cdot L)\),其中 \(L = 3j\) 为模式长度,字符串比较为 \(O(L)\)
    • 空间复杂度:\(O(m)\),一维 DP 数组
  5. 线性 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
posted @ 2026-07-17 15:26  团爸讲算法  阅读(5)  评论(0)    收藏  举报