题目描述:
ZZT 得到了一个字符串 S 以及一个整数 K。
WZH 在 1995 年提出了“优雅 K 串”的定义:这个字符串每一种字符的个数都是 K 的倍数。
现在 ZZT 想要对字符串进行 Q 次询问,第 i 次询问给出一个区间 [Li, Ri],他想计算 [Li, Ri] 中有多少个子串是“优雅 K 串”。
由于 ZZT 忙于工作,所以他把这个问题交给了你,请你帮忙解决。
输入描述:
第一行输入一个正整数 K。
第二行输入一个字符串 S。
第三行输入一个正整数 Q,表示有 Q 次询问。
接下来 Q 行,每行输入两个正整数 Li 和 Ri,表示第 i 次询问。
1 ≤ K ≤ 50.
1≤ | S | ≤ 3 x 104 且 S 仅包含小写英文字母.
1≤ Q ≤ 3 x 104.
1 ≤ Xi ≤ Yi ≤ N.
输出描述:
每次询问,输出一个正整数,表示满足条件的“优雅 K 串”的数量。
示例1
输入
1
abc
3
1 3
1 2
2 3
输出
6
3
3
分析:
一开始我是不知道如何去查询,看了题解才知道原来还有一种算法叫做莫队(%%%)。
莫队算法的核心如下:
int l = 1 , r = 0;
for(int i = 1; i <= m; i ++) {
while(l < ask[i].left) del(l ++);
while(l > ask[i].left) add(-- l);
while(r < ask[i].right) add(++ r);
while(r > ask[i].right) del(r --);
Ans[ask[i].id] = ans;
}
针对不同的题目,需要自己去写del和add两个函数,这里我就不赘述更多关于莫队算法的地方了,主要来说一下如何在本题中实现。
因为这道题目是需要求区间内有多少子串是符合题意的,通过构造前缀和,可以在O(1)的时间复杂度里面求得答案,这个需要想到。
#include <iostream>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <map>
#include <set>
#include <cstring>
#include <stack>
#include <string>
using namespace std;
typedef long long ll;
const int N = 3e4+199;
const double Pi = acos(-1);
char str[N];
int blo;
ll a[N],pre_num[N][30],ans[N],base=31,sum;
map<ll,int>ma;
struct node{
int l,r,id;
bool operator < (const node & a) const{
return l/blo==a.l/blo? r<a.r : l/blo<a.l/blo;
}
}num[N];
void Add(int x){
sum+=ma[x]++;//通过观察满足题意的序列可以得到一个递推关系,即1-->3-->6-->10
//然而这个关系正好是1+2+3+4+……
}
void Minus(int x){
sum-=--ma[x];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif // ONLINE_JUDGE
int Q,k;
scanf("%d\n",&k);
scanf("%s%d",str+1,&Q);
int len = strlen(str+1);
blo = sqrt(len+1);
for(int i=1;i<=len;i++){
for(int j=0;j<26;j++){
pre_num[i][j]=pre_num[i-1][j];
}
pre_num[i][str[i]-'a']=(pre_num[i][str[i]-'a']+1)%k;
//这里%k的原因是为了说明当pre_num[i][j]=0的时候,是满足题意
//等于0相当于是一个起点,更好的体现是在莫队算法的初始状态
//如果不模k,我们就不知道满足题意的初始状态是多少,也就无法在Add或Minus两个函数中迭代答案了;
}
for(int i=1;i<=len;i++){
ll pp = 0;
for(int j=0;j<26;j++){
pp = pp*base + pre_num[i][j];
}
a[i]=pp;//我们将每个位置的26个英文字母看成一个点,这样就可以实现哈希记录
}
for(int i=0;i<Q;i++){
scanf("%d%d",&num[i].l,&num[i].r);
num[i].l--;//这里之所以减一,就需要结合前缀和来理解了,要求[1,3]的答案,那就需要用b[3]-b[0]
num[i].id=i;
}
sort(num,num+Q);
int l=0,r=-1;
for(int i=0;i<Q;i++){
while(r<num[i].r) Add(a[++r]);
while(r>num[i].r) Minus(a[r--]);
while(l>num[i].l) Add(a[--l]);
while(l<num[i].l) Minus(a[l++]);
ans[num[i].id]=sum;
}
for(int i=0;i<Q;i++){
printf("%lld\n",ans[i]);
}
return 0;
}