洛谷 P11965:[GESP202503 七级] 等价消除 ← 位运算(异或) + STL map

news/2025/11/12 23:26:31/文章来源:https://www.cnblogs.com/triwa/p/19215792

【题目来源】
https://www.luogu.com.cn/problem/P11965

【题目描述】
小 A 有一个仅包含小写英文字母的字符串 S。
对于一个字符串,如果能通过每次删去其中两个相同字符的方式,将这个字符串变为空串,那么称这个字符串是可以被等价消除的。
小 A 想知道 S 有多少子串是可以被等价消除的。
一个字符串 S′ 是 S 的子串,当且仅当删去 S 的某个可以为空的前缀和某个可以为空的后缀之后,可以得到 S′。

【输入格式】
第一行,一个正整数 ∣S∣,表示字符串 S 的长度。
第二行,一个仅包含小写英文字母的字符串 S。

【输出格式】
一行,一个整数,表示答案。

【输入样例】
7
aaaaabb

【输出样例】
9

【数据范围】
对于 20% 的测试点,保证 S 中仅包含 a 和 b 两种字符。
对于另外 20% 的测试点,保证 1≤∣S∣≤2000。
对于所有测试点,保证 1≤∣S∣≤2×10^5。​​​​​​​

【算法分析】
● 异或运算的基本性质
异或运算(XOR)具有以下重要性质:
交换律‌:a ^ b = b ^ a
结合律‌:a ^ (b ^ c) = (a ^ b) ^ c
自反性‌:a ^ a = 0
零元素‌:a ^ 0 = a
可逆性‌:如果 a ^ b = c,那么 a = c ^ b

● 前缀异或和
前缀异或和是一种基于异或运算的前缀和技术,用于高效处理子区间异或相关的问题。
前缀异或和 s 定义为:s[i] = a[1] ^ ... ^ a[i],其中 ^ 表示异或运算。特别地,s[0] = 0。

● 前缀异或和重要性质
求证:若定义前缀异或和 s[i]=a[1]^a[2]^⋯^a[i],则子区间 [le,ri] 的异或和等于 s[ri]^s[le−1]
证明:由前缀异或和的定义,知:s[ri] = a[1]^a[2]^⋯^a[ri],s[le−1] = a[1]^a[2]^⋯^a[le−1]。
又已知 区间 [le,ri] 的异或和等于 a[le]^⋯^a[ri]。
所以 s[ri]^s[le−1] = (a[1]^⋯^a[ri]) ^ (a[1]^⋯^a[le−1])
= (a[1]^⋯^a[le−1]) ^ (a[1]^⋯^a[le−1]^a[le]^⋯^a[ri])
= (a[1]^⋯^a[le−1]) ^ (a[1]^⋯^a[le−1]) ^ (a[le]^⋯^a[ri])
= 0 ^ (a[le]^⋯^a[ri])
= a[le]^⋯^a[ri]
综上,得证。

● 直接统计每个字符的频次,在需要处理大量子串或动态问题时效率不高。异或方法提供了一种极其高效的‌状态压缩‌技巧。​​​​​​​因为我们不关心每个字符具体出现了多少次,只关心它出现次数的‌奇偶性‌。而异或运算 ^ 的规则正好完美地模拟了奇偶性的变化:
0 ^ 1 = 1(偶 + 奇 = 奇)
1 ^ 1 = 0(奇 + 奇 = 偶)
0 ^ 0 = 0(偶 + 偶 = 偶)
基于存在数学定理“如果一个字符串中每个字符出现次数都是偶数,那么‌必定存在‌一个消除顺序,使得最终能消除为空串”,故在本题中,等价消除的判断方法为“一个字符串可以被等价消除当且仅当每个字符出现的次数都是偶数‌”。​​​​​​​既然是统计字符频次的奇偶性,故可以用异或操作来求解本题。 ​​​​​​​

● 设 preXor[i] 表示字符串 s[0..i-1] 的奇偶状态(用二进制位表示,每位代表一个字母的奇偶性,1 表示奇数次,0 表示偶数次)。那么子串 s[le..ri] 可等价消除 ⇔ preXor[le] == preXor[ri+1]。换句话说,如果 preXor[le] == preXor[ri],那么子串 s[le..ri-1] 可消除。所以问题转化为:在 preXor[0..n] 中,有多少对 (i, j) 满足 i<j 且 preXor[i] == preXor[j],其中 n 为字符串 s 的长度
例如,给定字符串 "aaaaabb",'a' 记为 1,'b' 记为 2,初始状态 preXor[0] = 0,其计算过程如下:
i=0,字符 'a':state = 0 ^ 1 = 1,preXor[1] = 1。
i=1,字符 'a':state = 1 ^ 1 = 0,preXor[2] = 0。
i=2,字符 'a':state = 0 ^ 1 = 1,preXor[3] = 1。
i=3,字符 'a':state = 1 ^ 1 = 0,preXor[4] = 0。
i=4,字符 'a':state = 0 ^ 1 = 1,preXor[5] = 1。
i=5,字符 'b':state = 1 ^ 2 = 3,preXor[6] = 3。
i=6,字符 'b':state = 3 ^ 2 = 1,preXor[7] = 1。
综上,可得字符串 "aaaaabb" 的 preXor[] 数组值如下表所示。

i 0 1 2 3 4 5 6 7
preXor[i] 0 1 0 1 0 1 3 1

我们要找 preXor[i] == preXor[j] 且 i < j。观察上表可得,(0,2)(0,4)(2,4)(1,3)(1,5)(1,7)(3,5)(3,7)(5,7)等 9 个“数对”满足要求。它们分别对应着 9 个可等价消除的子串 s[0..1] = "aa"、s[0..3] = "aaaa"、s[2..3] = "aa"、s[1..2] = "aa"、s[1..4] = "aaaa"、s[1..6] = "aaaabb"、s[3..4] = "aa"、s[3..6] = "aabb"、s[5..6] = "bb" 等 9 个可等价消除的子串。

● 代码 x^=1<<(s[i]-'a'); 用位运算(异或和移位)高效地记录字符串中每个字符出现次数的奇偶性‌。本文代码中,利用 x^=1<<(s[i]-'a'); 计算所得的 x 值,就是上文提到的 preXor[] 数组值
(1)s[i] - 'a' 计算当前字符 s[i] 相对于字母 'a' 的偏移量。例如:
'a'-'a'=0,'b'-'a'=1,'c'-'a'=2,……,依此类推。
(2)1<<(s[i]-'a') 将数字 1 左移上面计算的偏移量。例如:
s[i]='a' → 1<<0=1(二进制 0001),
s[i]='b' → 1<<1=2(二进制 0010),
s[i]='c' → 1<<2=4(二进制 0100),……,依次类推。
(3)x ^= ... 将变量 x 与上面计算出的位掩码进行异或操作(0^0=0、0^1=1、1^0=1、1^1=0)。

例如,针对字符串 "abacaba",奇偶状态如下所示(0 表示偶数,1 表示奇数)。
初始状态:x=0000
遇到 'a':x=0000^0001=0001(a奇)
遇到 'b':x=0001^0010=0011(a奇b奇)
遇到 'a':x=0011^0001=0010(a偶b奇)
遇到 'c':x=0010^0100=0110(a偶b奇c奇)
遇到 'a':x=0110^0001=0111(a奇b奇c奇)
遇到 'b':x=0111^0010=0101(a奇b偶c奇)
遇到 'a':x=0101^0001=0100(a偶b偶c奇)

● 代码 cnt+=mp[x]++; 是统计‌可消除子串数量‌的核心逻辑​​​​​​​。mp[x] 记录的是状态 x 之前出现的次数,每次遇到相同的状态,就意味着找到了‌新的可消除子串‌。这些子串的起点分别是之前所有出现过该状态的位置。所以,当我们在位置 ri 遇到状态 x,且这个状态之前在位置 le 出现过,那么子串 s[le..ri-1] 就是可消除的

【算法代码】

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
map<int,LL> mp;
LL cnt;
string s;
int n,x;int main() {cin>>n>>s;mp[0]=1;for(int i=0; i<n; i++) {x^=1<<(s[i]-'a');cnt+=mp[x]++;}cout<<cnt;return 0;
}/*
in:
7
aaaaabbout:
9
*/





【参考文献】
https://www.luogu.com.cn/problem/solution/P11965
https://blog.csdn.net/hnjzsyjyj/article/details/154517364
https://blog.csdn.net/hnjzsyjyj/article/details/154310120
https://blog.csdn.net/hnjzsyjyj/article/details/154304346



 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/963851.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

*题解:P6617 查找 Search

原题链接 解析 考虑对于每个位置 \(i\) 维护最大的位置 \(pre_i < i\) 满足 \(a_i+a_{pre_i}=w\),这样区间 \([l,r]\) 内存在编号和为 \(w\) 的充要条件就为 \(\max_{i=l}^rpre_i \ge l\),可以使用线段树来维护。…

时序数据库的基本概念与原理:从核心到应用场景解析

一、时序数据库的核心概念 时序数据库( Time Series Database, TSDB ) 是一种专门用于存储、 管理和分析时间序列数据的数据库系统。 时间序列数据是指按时间顺序记录的数据点集合,通常具有以下特点: 时间维度为主…

C 指针数组函数之间的关联

可能经常会听到:指针常量、常量指针、指针数组、数组指针、指针函数、函数指针;函数指针数组,等这些听起来感觉向绕口令似的词汇; 可见数组、指针、函数之间是有很多联系的。比如看下面一段代码: #include<std…

2025.11.12 测试

2025.11.12 测试额 今天比较简单 感觉是 csp- 第一题,用线段树模拟贪心即可 当然也可以用三次单调队列,但没必要 第二题 额,线性基(模版?) 考虑答案是前面元素构成的线性空间 用 $ 2^{num} $ 即可 大样例是 \(2^…

13. 罗马数字转化为字符串

https://leetcode.cn/problems/roman-to-integer/ 难度:简单 题目说:I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。C 可以放在 D (500) 和 M (1000) …

逻辑回归(随笔)

核心思想:从回归到分类 想象一下,我们有一个简单的二分类问题(比如,根据肿瘤大小判断它是良性[0]还是恶性[1])。线性回归的困境:如果我们直接用线性回归 y = wx + b 来拟合,我们会用一条直线来拟合这些点。对于…

解析到本地127的神奇域名

作为一名 Web 开发者,我的日常工作就是在本地进行开发,实现各种功能。过去几年,我一直使用 127.0.0.1 作为本地服务的访问地址。当需要同时开发多个项目时,我会用不同端口来区分,例如:项目A:127.0.0.1:8080 项目…

这封邮件写得真好,是你自己写的吗? 不,是AI写的

本文通过一个真实职场场景,引出了职场中邮件写作的重要性和困难点,详细介绍了专门的AI邮件写作指令,通过实际案例展示了AI生成邮件的效果,并提供了使用技巧和注意事项,帮助职场人士快速提升邮件写作能力。昨晚11点…

FFmpeg 官方汇编课程:写出快 5 倍的视频处理代码

你的视频处理程序能跑通就行了? 同一个算法,有人用汇编优化后性能提升 5 倍。这不是天赋问题,而是技术盲区。FFmpeg 团队把内部汇编培训资料开源了,手把手教你写工业级高性能代码。这个项目教什么 asm-lessons 是 …

P14364 [CSP-S 2025] 员工招聘

考虑设 \(f_{i, j}\) 为前 \(i\) 个人死了 \(j\) 个,由于不知道哪些人选了所以无法转移。原因是前面的决策会影响后面的决策,所以考虑贡献延后计算。 会发现一个事情,对于当前 \(c_x \leq j\) 的东西之后不会再决策…

完整教程:【RabbitMQ】架构原理、消息丢失、重复消费、顺序消费、事务消息

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Ai元人文:尊严

这是一个极其精彩且深刻的问题。“尊严”恰恰是那种最抽象、最神圣,也最容易被空谈的价值。用价值原语来回答这个问题,能完美展现其方法论的力量。 当别人问“价值原语如何表示尊严?”时,您可以这样回答:“尊严”…

灵活用工-连续劳务-计算器工具类,拿走不谢

灵活用工-连续劳务-计算器工具类,拿走不谢新法令及对灵工行业的影响 国务院810号令及配套的国税总局15号、16号公告,共同构建了互联网平台税收治理的新框架,对灵活用工行业影响深远。这套组合拳旨在引导灵活用工行业…

四、中断(基于北京迅为电子)

一、概述中断上半部分和下半部分,中断上半部分处理紧急且需要快速响应的部分,中断下半部分处理耗时操作。 GIC控制器的四种中断类型,软件中断、私有外设中断、全局共享中断、特定的局部外设中断二、重要函数与中断申…

四、中断(基于北京迅为电子)

一、概述中断上半部分和下半部分,中断上半部分处理紧急且需要快速响应的部分,中断下半部分处理耗时操作。 GIC控制器的四种中断类型,软件中断、私有外设中断、全局共享中断、特定的局部外设中断二、重要函数与中断申…

List执行Dispose时可释放子元素逻辑占用的List写法

1、声明新的List类CanDisposeList/// <summary>/// 可释放子元素逻辑占用的List/// </summary>/// <typeparam name="T"></typeparam>public class CanDisposeList<T> : Obse…

Sora 后思考:从 AI 工具到 AI 平台,产业 AGI 又近了一步 - 指南

Sora 后思考:从 AI 工具到 AI 平台,产业 AGI 又近了一步 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "…

Scapy构建telnet包

Scapy构建telnet包文件代码 老师的 from scapy.all import *#我发第一次握手SYN ws1=IP(dst="172.16.100.101")/TCP(sport=10000,dport=23,flags=S,seq=1000)#对方发第二次握手 SA 包 ws2=sr1(ws1)#我发第三…

Spring AI Alibaba 项目源码学习(三)-Graph 执行流程分析

Graph 执行流程分析 概述 本文档分析 spring-ai-alibaba-graph-core 模块中 Graph 的执行流程,包括执行器(Executor)、调度机制、Checkpoint 机制和状态管理。 入口类说明 GraphRunner - 执行入口 GraphRunner 是基…