HDU4821 字符串哈希+尺取

题目链接:String

题目大意

给你一个字符串S,问你满足下面两个条件的子串有多少个?

  • 子串的长度必须是M×L;
  • M个长度为L的串必须互不相同,两个字符串不同是指任意一个位置不相同就算作不同。

数据范围

S长度不超过100000, 1 M L S的长度。

解题思路

首先可以采用字符串Hash将字符串处理便于储存,字符串Hash我是在网上找的一个方法,实际上是找一个基数Base,然后采用Base进制,可以采用模一个大质数,也可以是直接采用unsigned long long 爆了后自动模。这里是采用 unsigned long long。此题可以记下前缀Hash值,之后如果要找区间[l,r]这个串的Hash值,只需要Hash[r] - Hash[l - 1] * Pow[r - l + 1]。这是因为对于一个几进制数,比如10进制的12345,现在我已经求得Hash[1] = 1, Hash[2] = 12, Hash[3] = 123, Hash[4] = 1234, Hash[5] = 12345,, 然后我想求[3,5]的Hash值,那么因为是10进制一眼可以知道是345,那么计算机只能通过Hash[5] - Hash[2] * Pow[3];而Pow[3]即10的3次方,即为12345 - 12 × 1000 = 345,因此,可以知道其他进制也是满足此公式。

处理完字符串的Hash之后,我首先想的是外层枚举i从 1 l e n M L + 1 , 然后内层枚举从i开始,M个长度为L的串是否有重复值,如果没有答案就++,之后就TLE,其实复杂度是 ( l e n M L + 1 ) M * map(log), TLE也确实正常。之后才知道i只需要枚举从 1 L 就行,然后内层枚举所有的长度为L的子串,并将它们的Hash顺序存入数组,之后采用尺取法,每M个看是否有M个不同的元素,如果是则答案++,此复杂度就到了 O ( L L e n / L ) l o g ( U L L ) 即为 O ( l e n l o g )

AC代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<string>
#include<map>
#include<algorithm>
using namespace std;
typedef unsigned long long ULL;
const int maxn = 100000;
const ULL base = 233ULL;
ULL Hash[maxn + 5], Pow[maxn + 5];
ULL num[maxn + 5];
map<ULL, int>vis;
char s[maxn + 5];
int M, L, tot, res;
ULL Get_Hash(int l, int r) {
    return Hash[r] - Pow[r - l + 1] * Hash[l - 1];
}
void solve() {
    int cnt = 0;
    vis.clear();
    for(int i = 1; i <= M; i++) {
        if(!vis[num[i]])cnt++;
        vis[num[i]]++;
    }
    if(cnt == M)res++;
    int r = M + 1, l = 1;
    while(r <= tot) {
        vis[num[l]]--; if(vis[num[l]] == 0)cnt--; l++;
        if(vis[num[r]] == 0)cnt++; vis[num[r]]++; r++;
        if(cnt == M)res++;
    }
}
int main() {    
    Pow[0] = 1;
    for(int i = 1; i <= maxn; i++)Pow[i] = Pow[i - 1] * base;
    while(~scanf("%d%d", &M, &L)) {
        res = 0; scanf("%s", s + 1);
        int len = strlen(s + 1);
        Hash[0] = 1;
        for(int i = 1; i <= len; i++)Hash[i] = Hash[i - 1] * base + (ULL)(s[i] - 'a' + 1);
        for(int i = 1; i <= L; i++) {
            tot = 0;
            for(int j = i; j <= (len - L + 1); j += L)num[++tot] = Get_Hash(j, j + L - 1);
            solve();
        }
        printf("%d\n", res);
    }
    return 0;
}

另外基数Base要大于s[i],即满足进制规则。

猜你喜欢

转载自blog.csdn.net/qq_36889101/article/details/81011742