字符串Hash入门

引言

当我们对于一组值域较大但数据总数较少的数据无法处理时,其中一个处理方法就是对其进行离散化,把大值映射到小值内。但是,如果这组数据是由字符串组成的,我们又该如何对其处理?字符串Hash就是一种将字符串进行“离散化”,将字符串映射到一个可以接受的数值内,方便对数据进行处理。

做法

假设我们现在要将一个字符串\(S\)通过字符串\(Hash\)映射到一个值内。
我们可以将\(S\)看作一个\(P\)进制数,对\(S\)中的每个字符赋予一个权值。
假设\(S\)是由小写字符组成的,那么对于每一个字符,我们可以将\(a\)\(z\)赋值为\(1\)\(26\)
举个例子,当\(P\)\(26\)\(S\)\(aab\)时,它的Hash值就是\(1*26^2+1*26^1+2*26^0\),并对这个值适当取模。
对于一个字符串\(dsakhdkas\)来说,它的Hash值为\((4\ 19\ 1\ 11\ 8\ 4\ 11\ 1\ 19)_{26}\),则
\(Hash_0=0\)
\(Hash_1=4\)
\(Hash_2=4*26+19\)
\(Hash_3=4*26^2+19*26+1\)
\(Hash_4=4*26^3+19*26^2+1*26^1+11\)
\(Hash_n=...\)
如果要取\(S_{2-4}\)(sak),则它的\(Hash\)值为\((4\ 19\ 1\ 11)_{26}-(4\ 0\ 0\ 0)_{26}=(0\ 19\ 1\ 11)_{26}\),即\(Hash_4-Hash_1*26^3\)
故对于一个字符串\(S\)\(S_{l-r}\)\(Hash\)值为\(Hash_r-Hash_{l-1}*P^{r-l+1}\)
为了减少\(Hash\)的冲突,建议取一个质数作为\(P\),通常情况下取\(131\)\(13331\)。而且在方便且准确的情况下,我们可以直接将\(Hash\)数组和\(P\)的若干次方开为\(unsigned long long\)\(unsigned int\),使之自然溢出。

例题

兔子与兔子

模板题,直接上代码:

// 代码中f即为Hash数组,P为131的若干次方
#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ll;
#define mp make_pair
#define il inline
#define ri register int
#define rep(i, a, b) for (ri i = a; i <= b; ++ i)
#define per(i, a, b) for (ri i = a; i >= b; -- i)

const int N = 1000009;
int len, n;
ll p[N], f[N];
char s[N];

il char gc() {
	static char ibuf[1048576], *iS, *iT;
	return (iS == iT ? (iT = (iS = ibuf) + fread(ibuf, 1, 1048576, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++);
}

void readstr() {
	register char c = gc();
	while (c >= 'a' && c <= 'z') s[++ len] = c, c = gc();
}

void pre() {
	p[0] = 1;
	rep (i, 1, len) {
		p[i] = p[i - 1] * 131;
		f[i] = f[i - 1] * 131 + s[i] - 'a' + 1;
	}
}

template<class I>
il void read(I &x) {
	register char c = gc(); x = 0; ri f = 0;
	while(!isdigit(c)) f = (c == '-') ? 1 : f, c = gc();
	while(isdigit(c)) x = (x << 3) + (x << 1) + (c & 15), c = gc();
	x = f ? -x : x;
}

int main() {
    readstr();
    pre();
    read(n);
    while (n --) {
    	int l1, r1, l2, r2;
    	read(l1), read(r1), read(l2), read(r2);
    	if (f[r1] - f[l1 - 1] * p[r1 - l1 + 1] == f[r2] - f[l2 - 1] * p[r2 - l2 + 1]) puts("Yes");
    	else puts("No");
    }
    return 0;
}

再上一道模板题

珍珠项链

给出一个字符串\(S\),将其平均分成若干段,询问每段长度为多少时有最多不同的字符串数(每一段可以反转)

这题只要正反各做一遍\(Hash\),暴力枚举长度即可。时间复杂度\(O(nln_n)\)

#include <bits/stdc++.h>
using namespace std;

#define il inline
#define ri register int
#define rep(i, a, b) for (ri i = a; i <= b; ++i)
#define per(i, a, b) for (ri i = a; i >= b; --i)

const int N = 200009;
typedef unsigned long long ll;
int n, a[N], res, vis[N], cnt;
ll p[N], f1[N], f2[N];
vector<int> vec, output;
vector<ll> h[1000009];

il char gc() {
    static char ibuf[1048576], *iS, *iT;
    return (iS == iT ? (iT = (iS = ibuf) + fread(ibuf, 1, 1048576, stdin), (iS == iT ? EOF : *iS++)) : *iS++);
}

template <class I>
il void read(I &x) {
    register char c = gc();
    x = 0;
    ri f = 0;
    while (!isdigit(c)) f = (c == '-') ? 1 : f, c = gc();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c & 15), c = gc();
    x = f ? -x : x;
}

il ll hasha(int l, int r) { return f1[r] - f1[l - 1] * p[r - l + 1]; }
il ll hashb(int l, int r) { return f2[l] - f2[r + 1] * p[r - l + 1]; }
il int Hash(ll x) { return x % 999997 + 1; }

int check(int x1, ll y1, int x2, ll y2) {
    rep(i, 0, (int)h[x1].size() - 1) if (h[x1][i] == y1) return 0;
    rep(i, 0, (int)h[x2].size() - 1) if (h[x2][i] == y2) return 0;
    return 1;
}

int main() {
    read(n);
    p[0] = 1;
    rep(i, 1, n) {
        read(a[i]);
        p[i] = 19260817 * p[i - 1];
        f1[i] = f1[i - 1] * 19260817 + a[i];
    }
    per(i, n, 1) f2[i] = f2[i + 1] * 19260817 + a[i];
    rep(len, 1, n) {
        int ans = 0;
        cnt = 0;
        for (ri l = 1; l + len - 1 <= n; l += len) {
            int r = l + len - 1;
            ll u = hasha(l, r), v = hashb(l, r);
            int x = Hash(u), y = Hash(v);
            if (check(x, u, y, v)) {
                ++ans;
                h[x].push_back(u);
                vis[++cnt] = x;
                if (u != v) {
                    h[y].push_back(v);
                    vis[++cnt] = y;
                }
            }
        }
        if (ans > res) {
            output.clear();
            res = ans;
            output.push_back(len);
        } else if (ans == res)
            output.push_back(len);
        rep(i, 1, cnt) while (h[vis[i]].size()) h[vis[i]].pop_back();
    }
    printf("%d %d\n", res, output.size());
    rep(i, 0, (int)output.size() - 1) printf("%d ", output[i]);
    puts("");
    return 0;
}
posted @ 2020-11-17 21:21  ckn1023  阅读(95)  评论(0编辑  收藏  举报