字符串hash

字符串 hash

1.主要思想,将字符转化为一个权值

主要的处理方式有

  • 随机加
  • 随机乘
  • 随机取模

主要用的方式把一整个字符串看成一个b进制(自己规定)的数将这个数转化为该字符串的hash值,这样匹配的时候,可以将要求匹配的数也转换为hash值,然后就可以轻松做到\(O(1)\)的查询,但是由于预处理是\(O(n + m)\)的,总体的复杂度仍然为\(O(n + m)\)

注意:

  • hash的时候是有可能出错的,所以hash是一个不完美算法,但是算法竞赛能写几分算几分能骗几分算几分啊,数据水不就NB了吗一本通有说如果\(mod\) 的数为\(1e9+7 ||1e9+9\)几乎不可能发生冲突,原因是这两个数是孪生质数

主要流程

  • 对于一个字符串 \(s\) 定义一个hash函数对其映射,选取两个合适的质数 \(b~~ h~~(b~<~h)\)字符长度为len

\[hash(s) = ( \sum\limits_{i = 1}^{len} s_i \times b^{len - i})~~mod~~h \]

  • \(hash(s,k)\)为前\(k\)个字符构成的hash值,递推得到

\[hash(s,k+1) = hash(s,k) \times b + s_{k+1} \]

  • 如果匹配子串是否相等可以运用前缀和的思想,定义\(c\)为位置从\(k+1\)开始,长度为\(n\)的子串,子串的hash值可以得到为

\[hash(c) = hash(c_{k + n}) - hash(c_k) \times b ^n \]

  • 为了优化复杂度,我们要对\(b^?\)进行预处理(仅仅是为了应对多组数据)
  • 上面的是一种较为优秀的hash函数构造方式,先乘再取模

code

pow[0] = 1;
for(int i = 1 ; i <= len ;i++) 
    pow[i]=  pow[i - 1] * b;
  • 因为hash算法会出锅,所以如果允许可以的话写双hahs,一般双hash就卡不掉了(LB说的),但是会慢
  • 例题
    code
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#define ll long long  
#define ull unsigned long long  

using namespace std;
const int N = 1e6+100;
const int b = 1e9+7;
const int mod = 1e9+9;
int read() {
    int s = 0 , f = 0 ; char ch = getchar() ;
    while(!isdigit(ch)) f |= (ch == '-') , ch = getchar();
    while(isdigit(ch)) s = s * 10 + (ch ^ 48) , ch = getchar();
    return f ? -s : s;
}

char s1[N],s2[N];
ull sum[N];

ll q_pow(ll x, ll y) {
    ll ans = 1 ;
    while(y) {
        if(y&1) ans =(ans * x) % mod;
        x = (x * x) % mod , y >>= 1;
    }
    return  ans % mod;
}
int main() {
    scanf("%s%s",s1+1,s2+1);
    int len1 = strlen(s1 + 1), len2 =  strlen(s2+ 1);
    ll pow = q_pow(b,len2);
    sum[0] = 0;
    for(int i = 1 ; i <= len1 ;i++) 
        sum[i] = ((sum[i - 1] * b) % mod + s1[i]) % mod;
    ull  val = 0 ;
    for(int i = 1 ; i <= len2 ;i++) 
        val = ((val * b)% mod + s2[i] ) % mod ;
    int ans = 0 ;
    for(int i = 0 ; i <= len1 - len2 ;i++) {
        if(val == (sum[i + len2] - (sum[i] * pow) % mod + mod) % mod ) ans++;
    }
    printf("%d",ans);
    system("pause");
    return 0;
}

hash表

  • 一开始我也不知道这是个啥神仙东西,而且一本通写的是真的丑,理解了半天才看明白双hash;
  1. 双hash其实也不是难只要单hash明白了,图论学的还OK那就完全没问题
  2. 首先我喜欢结构体存图,所以我hash表是个结构体
int head[N] , num_h;
struct Hash{
    int diff,//different
    int net;//next
}hashs
  1. 第一个hash值为x,加入第二个一个hash为y的字符串
void add_h(int x ,int y) {
    hashs[++num_h].net = head[x];
    head[x] = num_h;
    hashs[num_h].diff = y;
}
  1. 匹配问题
bool query(int x,int y) {
    for(int i = head[x] ; i ; i = e[i].net) {
        if(hashs[i].diff == y) return true;
    }
    return false ;
}

例题

code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#define ll long long
using namespace std;
const int mod1 = 1e6+9;
const int b1 = 47;
const int mod2 = 1e6+6;
const int b2 = 79;
const int N = 3e6+100;

int read() {
    int s = 0 , f = 0 ; char ch = getchar() ;
    while(!isdigit(ch)) f |= (ch == '-') , ch = getchar();
    while(isdigit(ch)) s = s * 10 + (ch ^ 48) , ch = getchar();
    return f ? -s : s;
}
char opt[300];
int head[N],num_h;
struct HASHH{
    int diff,net;
}hashs[N];
void add(int x, int y){
    hashs[++num_h].net = head[x];
    head[x] = num_h;
    hashs[num_h].diff = y;
    return ;
}
bool query(int x, int y) {
    for(int i = head[x] ; i ; i = hashs[i].net){
        if(hashs[i].diff == y) return 1;
    }
    return 0;
}
 int main() {
    int n = read();
    for(int i = 1  ; i <= n ;i++) {
        cin >> opt;
        if(opt[0] == 'a') {
            gets(opt);
            int len = strlen(opt),sum1 = 0, sum2 = 0;
            for(int i = 0 ; i < len ;i++) {
                sum1 = (sum1 * b1 + opt[i]) % mod1;
                sum2 = (sum2 * b2 + opt[i]) % mod2;
            }
            add(sum1,sum2);
        }else {
            gets(opt);
            int len = strlen(opt),sum1 = 0 , sum2 = 0;
            for(int i = 0 ; i < len ;i++) {
                sum1 = (sum1 * b1 + opt[i]) % mod1;
                sum2 = (sum2 * b2 + opt[i]) % mod2;
            } 
            if(query(sum1,sum2)) cout<<"yes\n";
            else cout <<"no\n";
        }
    }
    system("pause");
    return 0;
}

hash拼接题
这个题最重要的一点就是如何拼接两个不连续的子串的hash值
根据原式子,
设原来的字符串为\(ABC\),删去B
\(hash\)值为$$(A \times b^2 + B \times b^1 + C \times b^0)%mod $$
去掉\(B\)后为$$(A \times b^1 + C \times b^0) % mod$$
稍微一对比就会发现,得到一个公式

  • 令前面的字符串为 \(M~~~hash\)值已知
  • 令后面拼接的字符串为\(N~~~hash\)值已知,长度为\(len\)
  • 那么\(MN\)\(hash\)值可以得到为

\[hash[M] *Pow[len] + hash[N] \]

那么这个题就很轻松就可以解决了,但是公式写的确实恶心

做法 :

直接对字符串进行暴力hash,复杂度为\(O(N)\)\(n/2+1\)为分界点,进行处理,关于\(AAAAAAA\)这种毒瘤数据需要以\(mid\)两边的答案进行比较对于这个比较有两种很好的方式,个人推荐第二种,但是可能会被卡,第一种也可能会被卡(可能性也与第一种差不多),但是第二个方法更暴力(STL),但是好想也好写

方法一 : 将左右两边的答案加到\(string\)类型的一个变量里 根据\(==\)直接判断
方法二 : 将左右两边在字符串的hash值求出,进行判读(随缘看数据,能不能过)

写写hash是真的被卡恶心了

  • 这个题中有一个非常优秀的性质 :
    如果发现在前一半中删去其中一个数等于后一半的hash值那么,在前一半中如果还有可能删去另一个等于后一半那么这两个串一定是相等的,同样可以推断后半部分也有这个性质,进一步推进,如果将这个\(for\)分成两部分来写,对于随机数据可以大大优化时间复杂度,甚至到不了\(O(N)\)但是如果卡的话还是会到\(O(N)\)带常数的一个级别,如果用方法二做,可以近似为常数不超过2,可以说非常优秀

方法一:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <map>
#define ll long long
using namespace std;
const int N = 2e6+100;
const int b = 1e9+7;
const int mod = 1e9 + 9;
int read() {
	int s = 0 , f = 0 ;
	char ch = getchar() ;
	while(!isdigit(ch)) f |= (ch == '-') , ch = getchar();
	while(isdigit(ch)) s = s * 10 + (ch ^ 48) , ch = getchar();
	return f ? -s : s;
}
char u[N];
ll hashs[N],Pow[N];
string ans1 ,temp,ans2;
int n ;
void prepare() {
	Pow[0] = 1 ;
	for(int i = 1 ; i <= 2e6 ; i++) Pow[i] = Pow[i - 1] * b % mod;
	return ;
}
void print() {
	// for(int i = 1 ; i <= 2e6 ; i++) cout << Pow[i] <<" ";puts("");
	for(int i = 1 ; i <= n ; i++) {
		cout << hashs[i]  <<" ";
	}
}
int main() {
	prepare();
	ans1 += '0';
	n = read();
	for(int i = 1 ; i <= n ; i++) {
		cin >> u[i];
		hashs[i] = ( hashs[i - 1] * b % mod + u[i] ) % mod;
	}
	int flag = 0 ,flag2 = 0,pos1 = 0, pos2 = 0;
	if( (n & 1) == 0) {
		cout <<"NOT POSSIBLE\n";
		return 0;
	} else {
		for(int i = 1 ; i <= n ; i++) {
			if(i <= n / 2 + 1) {
				if( flag == 0&&(( (hashs[n / 2 + 1] - hashs[i] * Pow[n / 2 + 1 - i] % mod + mod) % mod  + hashs[i - 1] * Pow[n / 2 + 1 - i] % mod+ mod ) % mod
				                == ( hashs[n] - hashs[n / 2 + 1] * Pow[n / 2] % mod + mod ) % mod) ) {
					pos1 = i,flag++;
					int cnt = 0;
					ans1.clear();
					for(int j = 1 ; cnt != n/2 ; j++) {
						if(j == pos1) continue;
						ans1 += u[j];
						cnt++;
					}
				}
			} else {
				if( flag2 == 0&&(( ( hashs[n] - hashs[i] * Pow[n - i] % mod + mod) % mod + (hashs[i - 1] - hashs[ n / 2 ] * Pow[i - 1 - n / 2 ] % mod + mod ) % mod * Pow[n - i] + mod ) % mod
				                 == ( hashs[n / 2] + mod ) % mod) ) {
					pos2 = i ,flag2++;
					int cnt = 0;
					for(int j = n/2+1 ; cnt != n/2 ; j++) {
						if(j == pos2) continue;
						ans2 += u[j];
						cnt++;
					}
				}
			}
		}
		if(flag + flag2 == 0&&(ans1 != ans2 || ans1 =="0" )) {
			cout <<"NOT POSSIBLE\n";
			return 0;
		}
		if(ans1 == ans2) {
			cout << ans1;

			return 0;
		}
		if(flag + flag2 > 1) {
			cout <<"NOT UNIQUE";
		} else {
			if(flag == 1) cout << ans1;
			else {
				cout << ans2;
			}
		}
		system("pause");
		return 0;
	}
}
/*
7
ABXCABC
*/

方法二:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <map>
#define ll long long
using namespace std;
const int N = 2e6+100;
const int b = 1e9+3;
const int mod = 1e9 + 7;
int read() {
	int s = 0 , f = 0 ;
	char ch = getchar() ;
	while(!isdigit(ch)) f |= (ch == '-') , ch = getchar();
	while(isdigit(ch)) s = s * 10 + (ch ^ 48) , ch = getchar();
	return f ? -s : s;
}
char u[N];
ll hashs[N],Pow[N];
ll ans1, ans2;
int n ;
void prepare() {
	Pow[0] = 1 ;
	for(int i = 1 ; i <= 2e6 ; i++) Pow[i] = Pow[i - 1] * b % mod;
	return ;
}
void print() {
	// for(int i = 1 ; i <= 2e6 ; i++) cout << Pow[i] <<" ";puts("");
	for(int i = 1 ; i <= n ; i++) {
		cout << hashs[i]  <<" ";
	}
}
int main() {
	prepare();
	n = read();
	for(int i = 1 ; i <= n ; i++) {
		cin >> u[i];
		hashs[i] = ( hashs[i - 1] * b % mod + u[i] ) % mod;
	}
	int flag = 0 ,flag2 = 0,pos1 = 0, pos2 = 0;
	if( (n & 1) == 0) {
		cout <<"NOT POSSIBLE\n";
		return 0;
	} else {
		for(int i = 1 ; i <= n ; i++) {
			if(i <= n / 2 + 1) {
				if( flag == 0&&( (hashs[n / 2 + 1] - hashs[i] * Pow[n / 2 + 1 - i] % mod + mod) % mod  + hashs[i - 1] * Pow[n / 2 + 1 - i] % mod+ mod ) % mod
				                == ( hashs[n] - hashs[n / 2 + 1] * Pow[n / 2] % mod + mod ) % mod)  {
					flag++;
					pos1 = i;
					ans1 = ((hashs[n / 2 + 1] - hashs[i] * Pow[n / 2 + 1 - i] % mod + mod) % mod  + hashs[i - 1] * Pow[n / 2 + 1 - i] % mod+ mod ) % mod;
				}
			} else {
				if( flag2 == 0&&( ( ( hashs[n] - hashs[i] * Pow[n - i] % mod + mod) % mod + (hashs[i - 1] - hashs[ n / 2 ] * Pow[i - 1 - n / 2 ] % mod + mod ) % mod * Pow[n - i] + mod ) % mod
				                 == ( hashs[n / 2] + mod ) % mod) ) {
					flag2++;
					pos2 = i;
					ans2 = ( ( hashs[n] - hashs[i] * Pow[n - i] % mod + mod) % mod + (hashs[i - 1] - hashs[ n / 2 ] * Pow[i - 1 - n / 2 ] % mod + mod ) % mod * Pow[n - i] + mod ) % mod;
				}
			}
		}
	}
	if((flag + flag2 == 0)&& (ans1 != ans2 || ans1 + ans2 == 0)) {
		cout <<"NOT POSSIBLE\n";
		return 0;
	}
//	cout << ans1 <<" " << ans1;
	if(flag + flag2 > 1 && ans1 != ans2) {
		cout <<"NOT UNIQUE";
		return 0;
	} else {
		if(flag == 1) {
			int cnt = 0;
			for(int i = 1  ; cnt != n/2 ; i++ ) {
				if(i == pos1) continue;
				cout<< u[i];
				cnt++;
			}
			return 0;
		} else {
			int cnt = 0;
			for(int i = n/2+1  ; cnt != n/2 ; i++ ) {
				if(i == pos2) continue;
				cout<< u[i];
				cnt++;
			}
			return 0;
		}
	}
	if(ans1 == ans2 && ans1 + ans2  != 0) {
		int cnt = 0;
		for(int i = 1  ; cnt != n/2 ; i++ ) {
			if(i == pos1) continue;
			cout<< u[i];
			cnt++;
		}
		return 0;
	}
	system("pause");
	return 0;
}
posted @ 2021-01-02 15:43  Imy_bisLy  阅读(306)  评论(0编辑  收藏  举报