字符串哈希

1 哈希

哈希(Hash)算法是一种能够将任意长度的输入转换成固定长度数据(哈希值)的算法。

它的主要特点如下:

  1. 输入的长度任意,但哈希值长度一定。
  2. 相同的数据经过哈希后哈希值相同,反之则不同。
  3. 哈希值的计算是单向的,也就是说,不能通过哈希值来得到原始数据。因此,哈希也被用于密码学,它可以增加密码的安全性和验证密码是否被篡改。

哈希的简单过程如下:

原始数据:12 8 9 14 3

7 7 7 为固定长度(通常是一个较大的质数),把每个数字填到对 7 7 7 取余后所对应的位置上:

0123456
1489312

哈希冲突: 两个数字需要填在相同的位置上,产生冲突。

例如,原始数据中再添加一个数字 19,此时 1912 冲突。

方法1 开放地址法: 在冲突位置开放溢出地址空间。

0123456
1489312(19)

方法2 开放寻址法: 从冲突位置向后找空余的地址,如果到尾部则从头开始,直到找到空间存放。

0123456
148931219

在哈希中,存值的复杂度为 O ( 1 ) O(1) O(1),而查找数据的过程,也可以看做近似于 O ( 1 ) O(1) O(1)

2 字符串哈希

字符串哈希: 将字符串每个字符转换为 ASCII \texttt{ASCII} ASCII 码,得到一个 128 128 128 进制数,将其转为 10 10 10 进制数,即为字符串哈希值。

例:hello! ⇒ \Rightarrow ( 104   101   108   108   111 ) 128 (104\ 101\ 108\ 108\ 111)_{128} (104 101 108 108 111)128 = = = ( 85666186 ) 10 (85666186)_{10} (85666186)10

代码如下:

long long Hash(string s) {
    has[0] = s[0] % mod;
    // pw[0] = 1;
    for (int i = 1; i < s.size(); i++) 
        has[i] = (has[i - 1] * 128 + s[i]) % mod,
        // pw[i] = pw[i - 1] * 128 % mod;
    return has[s.size() - 1];
}

另外,易得求子串哈希值的方法:

ll get(int l, int r) {
    return (has[r] - has[l] * pw[r - l + 1] % mod + mod) % mod;
}

其中, p w i = 12 8 i pw_i=128^{i} pwi=128i

通过字符串哈希,我们可以比较两个字符串是否完全相同。

3 例题1:查词典

题目描述

输入 n n n 行,每行一个英语单词和一个外文单词。

查询 m m m 次,查找外文单词的英文翻译(没有输出 eh)。

题解

求出每个外文单词的哈希值,查询时比较哈希值,相同时输出英文。

部分代码如下:

int main() {
    CLOSE;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> s[i] >> t;
        H[i] = Hash(t);
    }
    cin >> m;
    while (m--) {
        cin >> t;
        ll k = Hash(t), flag = 0;
        for (int i = 1; i <= n; i++) {
            if (H[i] == k) {
                cout << s[i] << endl;
                flag = 1;
                break;
            }
        }
        if (!flag) cout << "eh" << endl;
    }
    return 0;
}

4 例题2:消失的密文

题目描述

给定密码表 S S S ( ∣ S ∣ = 26 ) (\left|S\right|=26) (S=26), 先给出一条残缺后仅剩 n n n 个字母的密文,前一半为密文,后一半为前者的明文,但后面部分内容缺失,输出尽可能短的未残缺密文。

题解

显然,要找到密文的一个最短的前缀,使得前缀等于后缀的密文(前缀后缀等长)。

abcdefghijklmnopqrstuvwxyz
abcdab
qwertyuiopasdfghjklzxcvbnm
qwertabcde

样例中,第一组前缀 ab 是后缀 ab 的密文,则 abcd 为密文,还原后的完整密文是 abcdabcd

第二种前缀 qwert 是后缀 abcde 的密文,则 qwert 为密文,还原后的完整密文是 qwertabcde

代码如下:

#include <bits/stdc++.h>
#define endl '\n'
#define file(FILENAME) \
    freopen(FILENAME ".in", "r", stdin), freopen(FILENAME ".out", "w", stdout)
#define CLOSE                    \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int T, n;
string s, t;
int main() {
    CLOSE;
    cin >> T;
    while (T--) {
        map<char, char> mp;
        cin >> s >> t;
        for (int i = 0; i < 26; ++i)
            mp[s[i]] = 'a' + i;  //明文对照表
        n = t.size();
        ll ans1 = 0, jz = 1, ans2 = 0, end = n;
        for (int i = 0; i < n / 2; ++i) {
            ans1 = (ans1 + t[i] * jz) % mod;     //计算前缀的哈希值
            jz = jz * 128 % mod;
            ans2 = (ans2 * 128 + s[t[n - i - 1] - 'a']) % mod;  //计算后缀的哈希值
            if (ans1 == ans2) 
                end = n - i - 1;
        }
        for (int i = 0; i < end; ++i)
            cout << t[i];
        for (int i = 0; i < end; ++i)
            cout << mp[t[i]];  //补齐明文
        cout << endl;
        mp.clear();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值