题解:学而思编程 构建回文(二)

【题目来源】

学而思编程:构建回文(二)

【题目描述】

小猴有 \(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

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的字符串 \(S\)(仅含大写字母),\(q\) 次询问区间 \([l_i, r_i]\) 内的字符能否重排成回文串。回文的充要条件是:每种字符出现次数均为偶数,或恰好有一种字符出现奇数次(放中间)。这是一个前缀和 + 奇偶统计问题,关键在于快速统计区间内各字符出现次数的奇偶性。

  2. 算法选择

    • 二维前缀和cnt[i][c] 表示前 \(i\) 个字符中字母 \(c\) 的出现次数
    • 奇偶判定:区间 \([l, r]\) 内字母 \(c\) 的次数为 cnt[r][c] - cnt[l-1][c],判断其奇偶性
    • 回文条件:区间内出现奇数次的字母种类数 \(\leq 1\)
  3. 关键步骤

    • 初始化
      • 读取 \(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\) 的累计次数
    • 处理 \(q\) 次询问
      • 读取区间 \([l, r]\)
      • odd = 0:统计出现奇数次的字母种类数
      • 遍历 \(c\)\(0\)\(25\)
        • times = cnt[r][c] - cnt[l-1][c]:区间 \([l, r]\) 内字母 \(c\) 的出现次数
        • times % 2 == 1odd++
      • odd <= 1ans++(该区间可重排为回文)
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times 26 + q \times 26)\),前缀和构建 \(O(26n)\),每次询问 \(O(26)\)
    • 空间复杂度:\(O(n \times 26)\),二维前缀和数组 cnt[0..n][0..25]
  5. 前缀和与回文判定的核心思想

    • 回文充要条件:字符串可重排为回文,当且仅当至多一种字符出现奇数次(长度为奇数时中间字符可出现奇数次,长度为偶数时全为偶数次)
    • 区间次数快速查询:前缀和 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
posted @ 2026-07-04 13:31  团爸讲算法  阅读(5)  评论(0)    收藏  举报