HDU4821 字符串哈希+尺取

题目链接:String

题目大意

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

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

数据范围

S长度不超过100000, $ 1\leq M * L \leq $ 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 1~len - M * L + 1 1lenML+1, 然后内层枚举从i开始,M个长度为L的串是否有重复值,如果没有答案就++,之后就TLE,其实复杂度是 ( l e n − M ∗ L + 1 ) ∗ M (len - M * L + 1)*M (lenML+1)M * map(log), TLE也确实正常。之后才知道i只需要枚举从 1 ~ L 1~L 1L就行,然后内层枚举所有的长度为L的子串,并将它们的Hash顺序存入数组,之后采用尺取法,每M个看是否有M个不同的元素,如果是则答案++,此复杂度就到了 O ( L ∗ L e n / L ) ∗ l o g ( U L L ) O(L * Len / L) * log(ULL) O(LLen/L)log(ULL)即为 O ( l e n ∗ l o g ) O(len * log) O(lenlog)

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],即满足进制规则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值