字符串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
", res, output.size());
    rep(i, 0, (int)output.size() - 1) printf("%d ", output[i]);
    puts("");
    return 0;
}
原文地址:https://www.cnblogs.com/ckn1023/p/13980551.html