题解:学而思编程 构建回文(二)
【题目来源】
学而思编程:构建回文(二)
【题目描述】
小猴有 \(n\) 个水晶球排成一排,每一个水晶球的颜色都是红、橙、黄、绿、蓝等等26种颜色中的一种,这些水晶球从左到右依次编号为 \(1,2,...,n\)。
小猴最近刚刚学完回文相关知识点:如果一个字符串不论是从左向右看,还是从右向左看,内容都一样,那么这个字符串就是一个回文字符串。例如 \(ABBA\),\(AAA\),\(YRURY\) 和 \(Q\) 都是回文字符串,而 \(ABCA\)、\(ABAB\) 和 \(BA\) 则不是。
猴博士想要测试一下小猴是否完全理解了回文这个一知识点,向他提出了 \(q\) 个相关问题。其中第 \(i\) 个问题是:小猴能否使用编号在 \(l_i\) 到 \(r_i\) 之间(包括两个端点)的所有水晶球,经过任意的重排,使得 \(l_i\) 到 \(r_i\) 之间的所有水晶球在颜色上的排列能构成一个回文,即第 \(l_i\) 个水晶球颜色与第 \(r_i\) 个水晶球颜色相同,第 \(l_i+1\) 个水晶球颜色与第 \(r_i-1\) 个水晶球颜色相同,第 \(l_i-2\) 个水晶球颜色与第 \(r_i-2\) 个水晶球颜色相同,...。
请你帮助小猴统计 \(q\) 个问题中一共有多少个问题的结果是能够构成回文。
【输入】
第一行两个整数 \(n\),\(q\);
第二行一个长度为 \(n\) 的字符串 \(str\),表示排成一排的水晶球颜色,\(str\) 只包含大写字符 \(A\sim Z\),其中每一个字符对应一种不同的颜色。
接下来 \(q\) 行,每行两个整数 \(l_i,r_i\),表示一个问题。
【输出】
一行一个整数,表示q个问题中一共有多少个问题的结果是能够构成回文。
【输入样例】
9 3
ABBCAAATC
1 3
2 6
37
【输出样例】
2
【核心思想】
-
问题分析:给定长度为 \(n\) 的字符串 \(S\)(仅含大写字母),\(q\) 次询问区间 \([l_i, r_i]\) 内的字符能否重排成回文串。回文的充要条件是:每种字符出现次数均为偶数,或恰好有一种字符出现奇数次(放中间)。这是一个前缀和 + 奇偶统计问题,关键在于快速统计区间内各字符出现次数的奇偶性。
-
算法选择:
- 二维前缀和:
cnt[i][c]表示前 \(i\) 个字符中字母 \(c\) 的出现次数 - 奇偶判定:区间 \([l, r]\) 内字母 \(c\) 的次数为
cnt[r][c] - cnt[l-1][c],判断其奇偶性 - 回文条件:区间内出现奇数次的字母种类数 \(\leq 1\)
- 二维前缀和:
-
关键步骤:
- 初始化:
- 读取 \(n\)(字符串长度)和 \(q\)(询问次数)
- 读取字符串 \(S\),下标调整为从 \(1\) 开始
- 构建二维前缀和:
cnt[0][c] = 0(\(0 \leq c < 26\))- 遍历 \(i\) 从 \(1\) 到 \(n\):
- 遍历 \(c\) 从 \(0\) 到 \(25\):
cnt[i][c] = cnt[i-1][c] + (S_i == c):前 \(i\) 个字符中字母 \(c\) 的累计次数
- 遍历 \(c\) 从 \(0\) 到 \(25\):
- 处理 \(q\) 次询问:
- 读取区间 \([l, r]\)
odd = 0:统计出现奇数次的字母种类数- 遍历 \(c\) 从 \(0\) 到 \(25\):
times = cnt[r][c] - cnt[l-1][c]:区间 \([l, r]\) 内字母 \(c\) 的出现次数- 若
times % 2 == 1:odd++
- 若
odd <= 1:ans++(该区间可重排为回文)
- 输出
ans
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times 26 + q \times 26)\),前缀和构建 \(O(26n)\),每次询问 \(O(26)\)
- 空间复杂度:\(O(n \times 26)\),二维前缀和数组
cnt[0..n][0..25]
-
前缀和与回文判定的核心思想:
- 回文充要条件:字符串可重排为回文,当且仅当至多一种字符出现奇数次(长度为奇数时中间字符可出现奇数次,长度为偶数时全为偶数次)
- 区间次数快速查询:前缀和
cnt[i][c]存储累计次数,区间查询cnt[r][c] - cnt[l-1][c]实现 \(O(1)\) - 奇偶性分离:只需判断次数的奇偶,无需具体数值,但前缀和存储具体次数后取模同样高效
- 离线预处理:先一次性构建所有位置的前缀和,后续每次询问 \(O(26)\) 即可回答,适合多查询场景
- 适用于"多区间字符频率统计 + 回文判定"类问题,前缀和将重复遍历优化为常数查询
【算法标签】
前缀和
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, q;
cin >> n >> q; // 输入字符串长度和查询次数
string s;
cin >> s; // 输入字符串
s = 'a' + s; // 在开头添加字符,使索引从1开始
int sum = 0; // 满足条件的查询数量
int lcnt[500005][26]; // 二维前缀和数组,会占用很大内存
// 初始化第一行
for (int j = 0; j < 26; j++)
{
lcnt[0][j] = 0;
}
// 计算前缀和
for (int i = 1; i <= n; i++) // 遍历每个字符
{
// 前i个字符中,每个字母的出现次数
for (int j = 0; j < 26; j++) // 遍历26个字母
{
lcnt[i][j] = lcnt[i - 1][j] + (s[i] - 'A' == j);
}
}
while (q--) // 处理每个查询
{
int l, r;
cin >> l >> r; // 输入查询区间
int tong = 0; // 奇数次出现的字母个数
for (int i = 0; i <= 25; i++) // 检查每个字母
{
// 计算区间[l,r]内字母i的出现次数
int cnt = lcnt[r][i] - lcnt[l - 1][i];
if (cnt % 2 == 1) // 如果是奇数次
{
tong++;
}
}
if (tong <= 1) // 最多只有一个字母出现奇数次
{
sum++; // 满足条件
}
}
cout << sum; // 输出满足条件的查询数量
return 0;
}
【运行结果】
9 3
ABBCAAATC
1 3
2 6
3 7
2
浙公网安备 33010602011771号