牛客 K串 哈希+前缀和+莫队

题目链接

题目描述:
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;
}

猜你喜欢

转载自blog.csdn.net/c___c18/article/details/115522561