2023信奥赛C++提高组csp-s复赛真题及题解:消消乐

2023信奥赛C++提高组csp-s复赛真题及题解:消消乐

题目描述

小 L 现在在玩一个低配版本的消消乐,该版本的游戏是一维的,一次也只能消除两个相邻的元素。

现在,他有一个长度为 n n n 且仅由小写字母构成的字符串。我们称一个字符串是可消除的,当且仅当可以对这个字符串进行若干次操作,使之成为一个空字符串。

其中每次操作可以从字符串中删除两个相邻的相同字符,操作后剩余字符串会拼接在一起。

小 L 想知道,这个字符串的所有非空连续子串中,有多少个是可消除的。

输入格式

输入的第一行包含一个正整数 n n n,表示字符串的长度。

输入的第二行包含一个长度为 n n n 且仅由小写字母构成的的字符串,表示题目中询问的字符串。

输出格式

输出一行包含一个整数,表示题目询问的答案。

输入输出样例 1
输入 1
复制代码
8
accabccb
输出 1
复制代码
5
说明/提示

【样例 1 解释】

一共有 5 5 5 个可消除的连续子串,分别是 ccaccaccbccbaccabccb

【数据范围】

对于所有测试数据有: 1 ≤ n ≤ 2 × 10 6 1 \le n \le 2 \times 10^6 1≤n≤2×106,且询问的字符串仅由小写字母构成。

测试点 n ≤ n\leq n≤ 特殊性质
1 ∼ 5 1\sim 5 1∼5 10 10 10
6 ∼ 7 6\sim 7 6∼7 800 800 800
8 ∼ 10 8\sim 10 8∼10 8000 8000 8000
11 ∼ 12 11\sim 12 11∼12 2 × 10 5 2\times 10^5 2×105 A
13 ∼ 14 13\sim 14 13∼14 2 × 10 5 2\times 10^5 2×105 B
15 ∼ 17 15\sim 17 15∼17 2 × 10 5 2\times 10^5 2×105
18 ∼ 20 18\sim 20 18∼20 2 × 10 6 2\times 10^6 2×106

特殊性质 A:字符串中的每个字符独立等概率地从字符集中选择。

特殊性质 B:字符串仅由 ab 构成。

思路分析

本题要求统计字符串的所有非空连续子串中可消除的个数。一个字符串可消除,当且仅当可以通过反复删除相邻的相同字符,最终变为空串。

直接枚举所有子串并模拟消除过程的时间复杂度为 O(n²),无法通过 n ≤ 2×10⁶ 的数据。需要线性或接近线性的算法。

关键转化:用栈模拟消除过程。维护一个栈,遍历字符串,若当前字符等于栈顶字符则弹出栈顶,否则压入当前字符。遍历完成后栈为空,则整个字符串可消除。

对于子串 s[l...r],其可消除等价于:从 l 开始模拟消除过程,结束时栈为空。这等价于从 1 到 l-1 的栈状态与从 1 到 r 的栈状态相同。因为从 l 开始模拟相当于初始栈为空,处理完 s[l...r] 后栈为空,意味着从 l-1 的状态出发,处理 s[l...r] 后状态不变,所以 f[l-1] = f[r],其中 f[i] 表示处理完前 i 个字符后的栈状态。

因此,问题转化为:求有多少对 (l, r) 满足 1 ≤ l ≤ r ≤ n 且 f[l-1] = f[r]。对于每个 r,统计有多少个 l 满足 f[l-1] = f[r],累加即可。

实现细节

  • 用哈希值表示栈状态,便于比较和存储。
  • 从左到右扫描,用栈维护当前状态,同时记录每个位置的哈希值。
  • 使用哈希表记录每个哈希值出现的次数,扫描时累加以当前位置结尾的可消除子串数。
  • 注意初始状态 f[0] 也要计入。

代码实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull; // 自然溢出哈希

const int N = 2000005; // 最大长度
const int B = 131;     // 哈希基数

int n;
char s[N];            // 字符串(1-indexed)
char stk_c[N];        // 栈中字符
ull stk_h[N];         // 栈中记录的前一个哈希值
int top;              // 栈顶指针
unordered_map<ull, int> cnt; // 哈希值出现次数

int main() {
    scanf("%d", &n);
    scanf("%s", s + 1); // 从1开始读入

    ull h = 0;         // 当前哈希值
    cnt[0] = 1;        // 初始空栈状态出现一次(对应f[0])
    top = 0;
    ull ans = 0;       // 答案(可能很大,用ull)

    for (int i = 1; i <= n; i++) {
        char c = s[i];
        int v = c - 'a' + 1; // 字符映射为1~26

        if (top && stk_c[top] == c) { // 栈非空且栈顶字符相同,则弹出
            h = stk_h[top];           // 恢复弹出前的哈希值
            top--;
        } else {                      // 否则压栈
            top++;
            stk_c[top] = c;
            stk_h[top] = h;           // 记录压栈前的哈希值
            h = h * B + v;            // 更新哈希值
        }

        ans += cnt[h]; // 以i结尾的可消除子串数等于之前相同状态的出现次数
        cnt[h]++;      // 当前状态出现次数+1
    }

    printf("%llu\n", ans);
    return 0;
}

功能分析

  1. 核心算法:利用栈模拟消除过程,将子串可消除的条件转化为前缀状态相等,从而通过哈希和计数在线性时间内求解。
  2. 时间复杂度:O(n)。每个字符最多入栈和出栈一次,哈希表操作均摊 O(1)。
  3. 空间复杂度:O(n)。栈和哈希表最多存储 n 个状态。
  4. 注意事项
    • 使用自然溢出哈希,基数 B 取 131,字符映射为 1~26 以避免前导零问题。
    • 初始状态 f[0] 对应空栈,哈希值为 0,需预先加入计数。
    • 答案可能达到 n(n+1)/2,需使用 64 位无符号整数。

专栏推荐:信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html


各种学习资料,助力大家一站式学习和提升!!!

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"##########  一站式掌握信奥赛知识!  ##########";
	cout<<"#############  冲刺信奥赛拿奖!  #############";
	cout<<"######  课程购买后永久学习,不受限制!   ######";
	return 0;
}

1、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

2、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转

3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html

4、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
D_evil__2 小时前
【Effective Modern C++】第四章 智能指针:18. 使用独占指针管理具备专属所有权的资源
c++
草莓熊Lotso2 小时前
从零手搓实现 Linux 简易 Shell:内建命令 + 环境变量 + 程序替换全解析
linux·运维·服务器·数据库·c++·人工智能
进击的荆棘3 小时前
优选算法——滑动窗口
c++·算法·leetcode
_F_y10 小时前
MySQL用C/C++连接
c语言·c++·mysql
兩尛10 小时前
c++知识点2
开发语言·c++
xiaoye-duck10 小时前
C++ string 底层原理深度解析 + 模拟实现(下)——面试 / 开发都适用
开发语言·c++·stl
Azure_withyou11 小时前
Visual Studio中try catch()还未执行,throw后便报错
c++·visual studio
琉染云月11 小时前
【C++入门练习软件推荐】Visual Studio下载与安装(以Visual Studio2026为例)
c++·visual studio
L_090712 小时前
【C++】高阶数据结构 -- 红黑树
数据结构·c++