P3604 美好的每一天

题目背景

时间限制3s,空间限制162MB

素晴らしき日々

我们的情人,不过是随便借个名字,用幻想吹出来的肥皂泡,把信拿去吧,你可以使假戏成真。我本来是无病呻吟,漫无目的的吐露爱情---现在这些漂泊不定的鸟儿有地方栖息了,你可以从信里看出来。拿去吧---由于不是出自真心,话就说得格外动听,拿去吧,就这么办吧...

由于世界会在7月20日完结,作为救世主,间宫卓司要在19日让所有人回归天空

现在已经是19日傍晚,大家集合在C栋的天台上,一共n个人

在他们面前,便是终之空,那终结的天空

P3604 美好的每一天(莫队+前缀和)_洛谷

题目描述

回归天空是一件庄重的事情,所以卓司决定让大家分批次进行,给每个人给了一个小写字母\('a'->'z'\)作为编号

一个区间的人如果满足他们的编号重排之后可以成为一个回文串,则他们可以一起回归天空,即这个区间可以回归天空

由于卓司是一个喜欢妄想的人,他妄想了m个区间,每次他想知道每个区间中有多少个子区间可以回归天空

因为世界末日要来了,所以卓司的信徒很多

P3604 美好的每一天(莫队+前缀和)_洛谷_02

输入格式

第一行两个数\(n,m\)

之后一行一个长为\(n\)的字符串,代表每个人的编号

之后\(m\)行每行两个数\(l,r\)代表每次卓司妄想的区间

输出格式

\(m\)行,每行一个数表示答案

输入输出样例

输入 #1

6 6

zzqzzq

1 6

2 4

3 4

2 3

4 5

1 1

输出 #1

16

4

2

2

3

1

输入 #2

6 6

aaabbb

1 2

2 3

3 4

4 5

5 6

1 6

输出 #2

3

3

2

3

3

17

输入 #3

4 1

yuno

1 4

输出 #3

4

说明/提示

对于10%的数据,\(n,m<=100\)

对于30%的数据,\(n,m<=2000\)

对于100%的数据,\(n,m<=60000\)

字符集大小有梯度

在大家回归天空之后,彩名露出了阴冷的笑容

P3604 美好的每一天(莫队+前缀和)_#define_03

Solution

简化一下题意,给你一个区间,让你求它的子区间内的序列是回文串的个数

ps:这里说的回文串是指序列任意重排后是回文串


既然是 重排 ,也就是说一段序列是回文串当且仅当这段序列所有字符出现的次数要么全是偶数,要么只有一个奇数

也就是这两种

abccba
abcba


那么我们用一个26位二进制数来表示当前区间内各字母出现次数的奇偶性,0表示偶数,1表示奇数

前缀和优化,通过异或操作来求出一个区间内各字母的奇偶性

char s; cin>>s;
s[i]=1<<(s-'a');
s[i]^=s[i-1];
这是求异或前缀和


到了这里,做这道题之前建议先看看这题​​P4462 [CQOI2018]异或序列​

这是​​题解​

接下来的大部分操作都和 异或序列 这道题一样

异或的性质

已知a^b=c 
则a^c=b,b^c=a


求一段序列\([l,r]\)的异或和就是\(s_{l-1}\bigoplus s_r\),其中\(s[]\)是我们刚才求的异或前缀和数组

下面就是莫队了

上面说过满足条件的区间一定是各字母出现次数全为偶数,转化成数字就是\(0\),或者是某个字母出现次数为基数,转化成数字就是这个26位二进制数中有且仅有一位为\(1\)

即区间的异或和要么为0,要么为2^x


结合之前异或的性质

\(a_x \bigoplus a_{x+1} \bigoplus ... \bigoplus a_y = k\)\(\Rightarrow\)\(s_{x-1}\bigoplus s_r=k\)\(\Rightarrow\)\(s_{r}\bigoplus k=s_{x-1}\)

这个\(k\)就是\(0\)和\(2^x\),共27个状态

当我们加入一个值\(a[x]\)时,我们想要知道当前所在区间有多少个\(a[i]\bigoplus a[x]=k\),其中\(i<x\),也就是\(a[x]\bigoplus k\)的个数,因为这些数都可以和\(a[x]\)异或起来得到\(k\)

il void add(int x) {
ans+=cnt[a[x]];
for (rg int i=0;i<26;i++)
ans+=cnt[a[x]^(1<<i)];
cnt[a[x]]++;
}


删除同理

il void remove(int x) {
cnt[a[x]]--;
ans-=cnt[a[x]];
for (rg int i=0;i<26;i++)
ans-=cnt[a[x]^(1<<i)];
}


至于为什么\(add()\)函数是先更新\(ans\),在\(cnt[]++\),而\(remove()\)函数是先\(cnt[]--\)再更新\(ans\),在于\(k=0\)的情况

当\(k=0\)时,\(a[x]\bigoplus k=a[x]\),如果我们先用\(ans\)减去贡献,再使\(cnt[]--\),答案会少一

原因就是我们要统计的其实是\(i<x\)的\(cnt[a[x]]\)的个数,不能把第\(x\)个位置上的值算进去,所以需要先\(cnt[a[x]]--\),再用\(ans\)减去贡献

还有一点这道题卡空间还卡时间

我们的\(cnt[(1<<26)+5]\)数组,开\(int\)是开不下的,我开的是\(short\),另外块的大小也不能开\(\sqrt n\),要开\(3\times \sqrt n\)

(你问我为啥,我也不知道,莫队块的大小我一直搞不清)

Code
#include<bits/stdc++.h>
#define il inline
#define rg register
#define lol long long
#define in(i) (i=read())
using namespace std;

const int N=6e4+5;

int read() {
int ans=0,f=1; char i=getchar();
while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+(i^48),i=getchar();
return ans*f;
}

int n,m,k,block,ans;
int a[N],sum[N];
short cnt[(1<<26)+5];

struct query{
int l,r,id,pos;
bool operator < (const query &a) const {
return pos==a.pos?r<a.r:pos<a.pos;
}
}t[N];

il void add(int x) {
ans+=cnt[a[x]];
for (rg int i=0;i<26;i++)
ans+=cnt[a[x]^(1<<i)];
cnt[a[x]]++;
}

il void remove(int x) {
cnt[a[x]]--;
ans-=cnt[a[x]];
for (rg int i=0;i<26;i++)
ans-=cnt[a[x]^(1<<i)];
}

int main() {
in(n), in(m); block=3*sqrt(n);
for (rg int i=1;i<=n;i++) {
rg char s; cin>>s;
a[i]=1<<(s-'a');
a[i]^=a[i-1];
}
for (rg int i=1,l,r;i<=m;i++) {
in(l), in(r);
t[i].l=l-1, t[i].r=r;
t[i].id=i;
t[i].pos=(l-1)/block+1;
}
sort(t+1,t+1+m);
for (rg int i=1,curl=1,curr=0;i<=m;i++) {
rg int l=t[i].l,r=t[i].r;
while(curl<l) remove(curl++);
while(curl>l) add(--curl);
while(curr<r) add(++curr);
while(curr>r) remove(curr--);
sum[t[i].id]=ans;
}
for (rg int i=1;i<=m;i++) cout<<sum[i]<<endl;
}


博主蒟蒻