HDU - 4821 String (字符串Hash)

题解

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

猜你喜欢

转载自blog.csdn.net/pengwill97/article/details/80887079