题解:洛谷 P14075 划分字符串
【题目来源】
洛谷:P14075 [GESP202509 六级] 划分字符串 - 洛谷
【题目描述】
小 A 有一个由 \(n\) 个小写字母组成的字符串 \(s\)。他希望将 \(s\) 划分为若干个子串,使得子串中每个字母至多出现一次。例如,对于字符串 street 来说,str + e + e + t 是满足条件的划分;而 s + tree + t 不是,因为子串 tree 中 e 出现了两次。
额外地,小 A 还给出了价值 \(a_1,a_2,…,a_n\),表示划分后长度为 \(i\) 的子串价值为 \(a_i\)。小 A 希望最大化划分后得到的子串价值之和。你能帮他求出划分后子串价值之和的最大值吗?
【输入】
第一行,一个正整数 \(n\),表示字符串的长度。
第二行,一个包含 \(n\) 个小写字母的字符串 \(s\)。
第三行,\(n\) 个正整数 \(a_1,a_2,…,a_n\),表示不同长度的子串价值。
【输出】
一行,一个整数,表示划分后子串价值之和的最大值。
【输入样例】
6
street
2 1 7 4 3 3
【输出样例】
13
【核心思想】
-
问题分析:给定字符串 \(s\)(长度 \(n\))和长度价值数组 \(a[1..n]\),要求将 \(s\) 划分为若干子串,每个子串中字母至多出现一次,最大化各子串长度对应价值之和。这是一个线性 DP 问题,核心在于枚举以 \(i\) 结尾的无重复字符子串,利用集合快速判重确定合法子串范围。
-
算法选择:
- 一维线性 DP:\(dp[i]\) 表示前 \(i\) 个字符的最大价值之和
- 集合判重:用
set维护当前子串中的字符,遇到重复字符即终止扩展,确保子串合法
-
关键步骤:
- 初始化:读取 \(n\)、字符串 \(s\)、价值数组 \(a[1..n]\),\(s = ' ' + s\) 使下标从 \(1\) 开始
- 边界:\(dp[1] = a_1\)(第一个字符单独成段)
- DP 计算(\(i\) 从 \(2\) 到 \(n\)):
- 初始值:\(dp[i] = dp[i-1] + a_1\)(将 \(s_i\) 单独划分为长度为 \(1\) 的子串)
- 枚举扩展:\(j\) 从 \(i-1\) 递减到 \(1\):
- 用
set维护子串 \(s[j..i]\) 中的字符集合 - 若 \(s_j\) 已在集合中:\(break\)(出现重复,子串 \(s[j..i]\) 不合法)
- 将 \(s_j\) 加入集合
- 状态转移:\(dp[i] = \max(dp[i], dp[j-1] + a_{i-j+1})\)(将 \(s[j..i]\) 作为长度为 \(i-j+1\) 的子串)
- 用
- 输出答案:\(dp[n]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\),外层 \(O(n)\),内层最坏 \(O(n)\),集合操作 \(O(1)\)
- 空间复杂度:\(O(n)\),\(dp\) 数组
-
线性 DP 的核心思想:
- 无后效性分割:\(dp[i]\) 仅依赖于 \(dp[j-1]\)(\(j \leq i\)),前面的划分不影响后续,满足 DP 最优子结构
- 合法子串约束:通过从 \(i\) 向左扩展并用集合判重,确保子串 \(s[j..i]\) 中字符不重复,遇到重复立即终止(更短的 \(j\) 必然也包含该重复字符)
- 枚举所有可能分割点:对每个 \(i\),尝试所有以 \(i\) 结尾的合法子串长度,取价值最大者
- 贪心不可行:价值数组 \(a\) 非单调,无法贪心选择最长或最短子串,必须枚举所有合法分割
- 适用于字符串分割、带约束的子串选择、一维序列划分类 DP 问题
【算法标签】
普及 #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型防止溢出
const int N = 100005; // 定义最大数组长度
int n; // 字符串长度
string s; // 输入的字符串(处理后前面加空格)
string t[N]; // 未使用的字符串数组(保留原代码结构)
int a[N]; // 存储每个位置的分值
int dp[N]; // dp数组,dp[i]表示前i个字符的最大得分
signed main()
{
// 输入字符串长度和字符串
cin >> n >> s;
// 在字符串前添加空格,使索引从1开始
s = " " + s;
// 输入每个位置的分值
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 初始化dp数组
dp[1] = a[1]; // 第一个字符的最大得分就是它本身的分值
// 动态规划计算最大得分
for (int i = 2; i <= n; i++)
{
set<char> st; // 用于记录当前子串中的字符
// 初始值:将当前字符单独作为一个子串
dp[i] = dp[i - 1] + a[1];
// 将当前字符加入集合
st.insert(s[i]);
// 尝试将当前字符与前面的字符组成更长的子串
for (int j = i - 1; j >= 1; j--)
{
// 如果遇到重复字符,则终止循环
if (st.count(s[j]))
{
break;
}
// 将字符加入集合
st.insert(s[j]);
// 更新dp值:考虑从j到i的子串
dp[i] = max(dp[i], dp[j - 1] + a[i - j + 1]);
}
}
// 输出前n个字符的最大得分
cout << dp[n] << endl;
return 0;
}
【运行结果】
6
street
2 1 7 4 3 3
13
浙公网安备 33010602011771号