1 条题解
-
0
E. 交替字符串 - 详细题解
问题重述
给定一个字符串 ,可以通过两种操作将其变为交替字符串(偶数长度,奇数位置字符相同,偶数位置字符相同):
- 删除操作:删除任意一个字符,最多执行 次
- 替换操作:将任意字符替换为其他字母,次数不限
求最少操作次数。
关键观察
观察 1:交替字符串的结构
交替字符串长度为偶数,设为 ,则:
- 所有奇数位置()的字符相同,设为
- 所有偶数位置()的字符相同,设为
- 和 可以相同也可以不同
观察 2:无删除操作的情况
如果原始字符串长度 为偶数,我们不需要删除操作(因为删除操作只能使用 或 次,且会使长度变为奇数,不符合要求)。
此时问题简化为:
- 选择奇数位置的统一字符
- 选择偶数位置的统一字符
- 最小化替换次数
计算方法:
- 奇数位置:保留出现次数最多的字符,其余替换
- 偶数位置:保留出现次数最多的字符,其余替换
观察 3:需要删除操作的情况
当 为奇数时,必须删除一个字符(因为交替字符串长度必须为偶数)。删除后字符串长度变为偶数。
删除位置 后:
- 位置 之前的字符奇偶性不变
- 位置 之后的字符奇偶性翻转(因为所有后面的字符向前移动一位)
算法设计
情况 1: 为偶数
直接统计奇数位和偶数位的字符频数,取最大值:
$$\text{ans} = n - \max_{c}(\text{odd}[c]) - \max_{c}(\text{even}[c]) $$情况 2: 为奇数
对于每个可能删除的位置 ,计算删除后的最少替换次数,取最小值。
预处理:
- :位置 之前(不包括 )的偶数位置中字符 的数量
- :位置 之前的奇数位置中字符 的数量
- :位置 之后(不包括 )的偶数位置中字符 的数量
- :位置 之后的奇数位置中字符 的数量
删除位置 后的奇偶分布:
- 新的偶数位置 = 删除前偶数位置且 + 删除前奇数位置且 (因为翻转)
- 新的奇数位置 = 删除前奇数位置且 + 删除前偶数位置且 (因为翻转)
因此,新偶数位置中字符 的数量为:
新奇数位置中字符 的数量为:
删除后字符串长度为 (偶数),最少替换次数为:
$$\text{cost} = (n-1) - \max_c(\text{new\_even}[c]) - \max_c(\text{new\_odd}[c]) $$
算法步骤
- 读入 和字符串
- 如果 为偶数:
- 统计奇数位和偶数位的字符频数
- 输出
- 如果 为奇数:
- 初始化后缀数组 ,统计所有位置的频数
- 初始化前缀数组
- 遍历每个位置 (从左到右):
- 从后缀中减去当前位置 的贡献
- 计算删除 后的最少替换次数
- 更新答案
- 将当前位置 的贡献加入前缀
- 输出最小值
完整代码
#include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while (t--) { int n; cin >> n; string s; cin >> s; int res = n; // 初始化为最大值 if (n % 2 == 0) { // 情况1:长度为偶数,不需要删除 vector<int> odd(26, 0), even(26, 0); for (int i = 0; i < n; i++) { if (i % 2 == 0) { even[s[i] - 'a']++; } else { odd[s[i] - 'a']++; } } int max_odd = *max_element(odd.begin(), odd.end()); int max_even = *max_element(even.begin(), even.end()); res = n - max_odd - max_even; } else { // 情况2:长度为奇数,需要删除一个字符 vector<int> pref[2] = {vector<int>(26, 0), vector<int>(26, 0)}; vector<int> suf[2] = {vector<int>(26, 0), vector<int>(26, 0)}; // 初始化后缀数组 for (int i = n - 1; i >= 0; i--) { suf[i % 2][s[i] - 'a']++; } // 遍历每个可能删除的位置 for (int i = 0; i < n; i++) { // 从后缀中移除当前位置 suf[i % 2][s[i] - 'a']--; // 计算删除当前位置后的最少替换次数 int ans = n; // 临时变量 for (int k = 0; k < 2; k++) { int mx = 0; for (int j = 0; j < 26; j++) { // k=0: 新偶数位置 = 前缀偶数 + 后缀奇数 // k=1: 新奇数位置 = 前缀奇数 + 后缀偶数 mx = max(mx, suf[1 - k][j] + pref[k][j]); } ans -= mx; } res = min(res, ans); // 将当前位置加入前缀 pref[i % 2][s[i] - 'a']++; } } cout << res << endl; } return 0; }
时间复杂度
- 每个测试用例:
- 总复杂度:$O(\sum n \times 26) \le 2 \times 10^5 \times 26 \approx 5.2 \times 10^6$
---直接相信标程的正确性,并按照标程实现即可。
- 1
信息
- ID
- 6329
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者