HDU - 4821 String (字符串Hash)

本文介绍了一种通过字符串Hash优化子串匹配效率的算法。针对特定条件下的子串查找问题,利用滑动窗口技巧实现O(1)的时间复杂度更新,有效避免了传统方法中的重复计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题解

给出M和L,和一个字符串S。要求找出S的子串中长度为L*M,并且可以分成M段,每段长L,并且M段都不相同的子串个数。
用字符串Hash来做,但是也不能太暴力。
如果我们枚举每个可能的子串,然后检查,这样复杂度近似于|s|2|s|2,肯定是会T的。要想办法进行优化。
易知,每个子串的长度一定是LML∗M的,按照朴素的方法,从SS开始,依次向右移动1个单位,成为新的子串,检查合法性。此时我们可以向右移动L个单位,不难发现,这样的话,变化只有一段,即去掉的头部LL个字符和新加入的L个字符。
利用这个特性,每移动一次,可以做到O(1)O(1)的修改,从而降低复杂度。

代码

#include "bits/stdc++.h"
using namespace std;
typedef unsigned long long ull;
const int nmax = 1e5 + 100 ;
const int INF = 0x3f3f3f3f;
const ull p = 97;
int M, L, len;
char str[nmax];
ull myhash[nmax], pp[nmax];
map<ull, int> mp;
inline void init() {
    pp[1] = p;
    for (int i = 2; i < nmax; ++i) pp[i] = pp[i - 1] * p;
}
inline void gethash() {
    myhash[1] = str[1];
    len = strlen(str + 1);
    for (int i = 2; i <= len; ++i) {
        myhash[i] = myhash[i - 1] * p + str[i];
    }
}
inline ull submyhash(int l, int r) {
    return myhash[r] - myhash[l - 1] * pp[r - l + 1];
}
int main() {
    init();
    while (scanf("%d%d", &M, &L) != EOF) {
        scanf("%s", str + 1);
        memset(myhash, 0, sizeof myhash);
        gethash();
        int ans  = 0;
        for (int i = 1;  i <= L && i + (M * L) - 1 <= len; ++i) {
            mp.clear();
            for (int ll = i; ll <= len - (M * L) + 1; ll += L) {
                int rr = ll + (M * L) - 1;
                if (ll == i) {
                    for (int l = ll; l <= rr; l += L) {
                        int r = l + L - 1;
                        mp[submyhash(l, r)] ++;
                    }
                } else {
                    int r = rr - L + 1;
                    mp[submyhash(r, rr)] ++;
                }
                if (mp.size() == M) ans ++;
                mp[submyhash(ll, ll + L - 1)]--;
                if (mp[submyhash(ll, ll + L - 1)] == 0)
                    mp.erase(submyhash(ll, ll + L - 1));
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值