哈希 专题

进制转换法

s u m [ i ] = ( s u m [ i − 1 ] ∗ p + s t r [ i ] ) % m o d ( i > = 1 ) ; sum[i]=( sum[i-1]*p+str[i])\%mod (i>=1); sum[i]=(sum[i1]p+str[i])%mod(i>=1);

调整方法(减少冲突):

进制 p p p一般取 131 131 131 13331 13331 13331 m o d mod mod一般取 1 e 9 + 7 1e9+7 1e9+7或者 1 e 9 + 9 1e9+9 1e9+9
这个也行 m o d = 212370440130137957 l l mod = 212370440130137957ll mod=212370440130137957ll // 是质数

因为取模是个效率极低的运算,所以我们把hash值储存在unsigned long long里面, 那样溢出时,会自动取余2的64次方,but这样可能会使2个不同串的哈希值相同,但这样的概率极低(不排除你的运气不好)。

注意:unsigned long long 数据精度很高,对格式的要求也更高,因此在做取模运算时,要注意使用一致的数据类型(哈希的时候数组都开ull)

取出字符串的子串的哈希值

在处理子串问题时,我们一般采用前缀和去维护哈希值
首先按照进制法则预处理出每一个前缀的哈希值, s t r [ i ] str[i] str[i]后边没必要 − ′ a ′ -'a' a,感觉 − ′ a ′ -'a' a之后哈希冲突的可能性大一些

for(int i = 1; i <= n; ++i) hash[i] = (hash[i-1] * p + str[i]) % mod

如果我们需要取出某段子串(str[l~r])的哈希值,代码如下:

inline ull getsub(int l, int r) {return hash[r] - hash[l - 1] * Pow[r - l + 1];} 

也就是 h a s h [ l hash[l hash[l ~ r ] = ( h a s h [ 1 r]=(hash[1 r]=(hash[1 ~ r ] r] r] − - h a s h [ 1 hash[1 hash[1 ~ ( l − 1 ) ] ∗ p o w ( p , r − l + 1 ) + m o d ) % m o d (l-1)] * pow(p,r-l+1)+mod)\%mod (l1)]pow(p,rl+1)+mod)%mod
这个 P o w Pow Pow数组也是 u l l ull ull 类型的, P o w [ i ] Pow[i] Pow[i]表示 p p p i i i 次方 64 64 64 位自然溢出的结果
预处理 p p p 的所有次方

Pow[0] = 1;
for(int i = 1; i <= maxn - 3; ++i)
	Pow[i] = Pow[i-1] * p % mod;

助于理解取出子串的过程

hash[1] = s1
hash[2] = s1 * p p p + s2
hash[3] = s1 * p 2 p^2 p2 + s2 * p p p + s3
hash[4] = s1 * p 3 p^3 p3 + s2 * p 2 p^2 p2 + s3 * p p p + s4
hash[5] = s1 * p 4 p^4 p4 + s2 * p 3 p^3 p3 + s3 * p 2 p^2 p2 + s4 * p p p + s5

子串3~4的哈希值, H a s h Hash Hash = ( h a s h [ 4 ] − h a s h [ 2 ] ∗ p o w ( p , 4 − 3 + 1 ) =(hash[4]-hash[2]*pow(p,4-3+1) =(hash[4]hash[2]pow(p,43+1) + + + m o d ) % m o d mod)\%mod mod)%mod

题目

求循环同构子串的数目
题意:
给定一个 T 串,然后 m 个 S 串,求这 m 个 S 串中有多少是与 T 串构造循环同构
思路:
把 T 串延长一倍,求所有长度为 n 的子串的哈希值
然后暴力即可
code:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const ull p = 13331;
const int maxn = 2e6 + 9;// 因为循环串,注意开2倍大小
ull Pow = 1;
ull Hash[maxn];
unordered_map <ull,int> ma;

ull getsub(int l, int r)
{
	return Hash[r] - Hash[l - 1] * Pow;
}
int main()
{
	ios::sync_with_stdio(false);
	string t;
	cin >> t;
	int n = t.length();
	for(int i = 1; i <= n; ++i) Pow *= p;
	t = ' ' + t + t;
	for(int i = 1; i < n * 2; ++i)
	{
		Hash[i] = Hash[i-1] * p + t[i] - 'a';
		if(i >= n) ma[getsub(i - n + 1, i)] = 1;
	}
	int m;
	cin >> m;
	while(m--)
	{
		string s;
		cin >> s;
		int k = s.length(), ans = 0;
		s = ' ' + s;
		for(int i = 1; i <= k; ++i)	Hash[i] = Hash[i-1] * p + s[i] - 'a';
		for(int i = n; i <= k; ++i) ans += ma[getsub(i - n + 1, i)];
		cout << ans << "\n";
	}
	return 0;
}

hdu 1880
哈希冲突解决办法:链地址法
学快排我感觉可以参考这个题解,所以就贴一下
输入输出巨恶心
大佬写的代码也有点抽象,勉强搞懂了
注意冲突是 x % c, 不是x, x不一定相等
题意:
每行输入一个字符串,含空格,前部分代表咒语名字,后部分代表内容
读入结束后 t 个查询
如果输入是 咒语名字,输出咒语内容
如果输入是 咒语内容,输出咒语名字
输入不存在的东西输出 what?

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#define endl '\n'
using namespace std;
const int c=10000;
struct myhash //评测机上hash是关键字
{
    struct node
    {
        long long key;//若key%c相同辅助判重
        string s1;//若key相同辅助判重
        string s2;//要查找的东西,字符串s1对应的字符串s2
        node* next;//链表用指针
    }a[c+5];// 一个链表数组,每个都可以向后插入节点
    void push(long long x,string s1,string s2)  //插入函数
    {
        if(a[x%c].key == 0)  //如果数组 x % c 处没有元素,直接插入
        {					// 注意冲突的是 x % c,不是 x
            a[x%c].key = x;
            a[x%c].s1 = s1;
            a[x%c].s2 = s2;
        }
        else    //如果有,链表插入新节点(头插入)
        {
            node *tmp = new node();
            tmp->key = x;
            tmp->s1 = s1;
            tmp->s2 = s2;
            tmp->next = a[x%c].next;//此处相当于head
            a[x%c].next = tmp;
        }
    }
    string found(long long x, string s1)  //查找函数
    {
        if(a[x%c].key == x && a[x%c].s1 == s1) return a[x%c].s2;//如果字符串在数组里,直接返回对应的字符串s2
        if(a[x%c].key==0) return "what?";//如果数组里没有元素,链表里肯定也没有,返回what?
        else
        {
            node* tmp=a[x%c].next;//遍历链表查找
            while(tmp!=NULL)
            {
                if(tmp->key == x && tmp->s1 == s1) return tmp->s2;
                tmp = tmp->next;
            }
            return "what?";// 链表里也没有
        }
    }
};
myhash H1,H2;//H1存储名字对应的内容,H2存储内容对应的名字
long long to_num(string str) //前8位的乘积hash
{
    long long ans = 1;
    int len = str.length();
    if(len > 8) len = 8;
    for(int i = 0; i < len; i++) ans *= str[i];
    return ans;
}
int main()
{
    int t;
    string str,s1,s2;
    while(1)
    {
        int i;
        getline(cin, str);//有空格,必须用getline
        if(str == "@END@") break;
        for(i = 0; i < str.length(); i++)
        {
            if(str[i] == ']') break;//找到咒语名字和内容的分界点
        }
        s1 = str.substr(0, i + 1);
        s2 = str.substr(i + 2, str.length() - i - 1);
        //将str分为两部分,s1是名字,s2是内容
        H1.push(to_num(s1), s1, s2);// s1前8位的哈希值对应s2
        H2.push(to_num(s2), s2, s1);// s2前8位的哈希值对应s1
    }
    cin >> t;
    getchar();//输入一个换行符,否则换行符会被getline读入到str中,导致在哈希表中查找'\n',多输出一个"what?"
    while(t--)
    {
        getline(cin, str);
        string tmp;
        if(str[0] == '[')	//区分输入的是内容还是名字
            tmp = H1.found(to_num(str), str);
		else
            tmp = H2.found(to_num(str), str);
		if(tmp[0] == '[')// 输出名字不带括号
		{
			for(int i = 1; i < tmp.length() - 1; ++i) cout << tmp[i];
			cout << endl;
		}
		else cout << tmp << endl;
    }
    return 0;
}




poj 3461
求可重叠串个数
题意:
s 串的子串中有多少 t 串
思路:
哈希一下,暴力即可
code:

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e6 + 9;
typedef unsigned long long ull;
const int P = 999983;
ull p[N], hash[N];// 一个p[N]存进制的i次方   hash[N]存所求字符串的哈希值
char s[N], t[N];// t是模板串   s是所求串
int main()
{
	int n;
	cin >> n;
	p[0] = 1;
	for(int i = 1; i <= N-10; ++i)
		p[i] = p[i-1] * P;
	while(n--)
	{
		scanf("%s%s",t+1, s+1);
		int l = strlen(s+1);
		
		for(int i = 1; i <= l; ++i)
			hash[i] = hash[i-1]*P + s[i];// 处理所求串的哈希值
		
		int ll = strlen(t+1);
		ull Ha = 0;
		for(int i = 1; i <= ll; ++i)
			Ha = Ha * P + t[i];// 求一下模板串的哈希值
		int ans = 0;
		for(int i = ll; i <= l; ++i)
		{
			//cout << i << " " << i-ll << endl;
			ull x = hash[i] - hash[i-ll]*p[ll] ;
			// 与模板串相同长度子串的哈希值
			if(Ha == x) ans++;
		}
		cout << ans << endl;
	}
	return 0;
}

poj 2406
求最大循环次数
题意:
给定 s 串
abcd — 1
aaaa — 4
ababab — 3
看样例比较好懂题意,就是找最大的循环次数,那么肯定要尽量让循环串小
先对 s 串哈希一遍
然后从小到大枚举满足条件可以作为循环串的长度
第一个满足条件的长度的循环串就是要找的循环串,s 串长度 / 循环串长度即为答案
code:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<vector>
using namespace std;
typedef unsigned long long ull;
const int N = 1e6 + 9;
ull P = 999983;
char s[N];
ull hash[N], p[N];
int main()
{
	p[0] = 1;
	for(int i = 1; i <= N - 9; ++i)
		p[i] = p[i-1] * P;
	while(~scanf("%s", s+1))
	{
		if(s[1] == '.') break;
		int l = strlen(s+1);
		for(int i = 1; i <= l; ++i)
			hash[i] = hash[i-1] * P + (ull)s[i];
		// 处理s的哈希值
		vector <int> v;
		for(int i = 1; i <= l; ++i)// 枚举可循环字符串长度
			if(l % i == 0) v.push_back(i);
		int ans = 1;
		for(int i = 0; i < v.size(); ++i)
		{
			bool f = 1;
			for(int j = v[i]; j <= l; j += v[i])
			{
				if(hash[j] - hash[j - v[i]] * p[v[i]] != hash[v[i]])
				{									// 从0开始到v[i]长度
					f = 0;break;
				}
			}
			if(f){
				ans = l / v[i];break;
			}
		}
		cout << ans << endl;
	}
	return 0;
}

poj 2752
求公共前后缀长度
这个题要算的是,给定一个字符串s,有哪些长度的s的前缀,同时也是s的后缀。

#include <iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define ull unsigned long long
using namespace std;
const int maxn=4e5+7;
char s[maxn];
const ull P=999983;
ull hash[maxn],p[maxn];
int main() 
{
	p[0]=1;
	for(int i=1;i<=maxn-3;++i)
		p[i]=p[i-1]*P;
	while(~scanf("%s",s+1))
	{
		int len=strlen(s+1);
		for(int i = 1;i <= len; ++i)
			hash[i] = hash[i-1] * P + (ull)s[i];
		vector<int> v;
		for(int i = 1;i <= len; ++i)
		{
			if(hash[i] == hash[len]-hash[len-i]*p[i])
				v.push_back(i);
		}
		for(int i = 0; i < v.size(); ++i)
		{
			cout << v[i];
			if(i!=v.size() - 1) cout << " ";
		}
		printf("\n");
	}
	return 0;
}

K - Unique Activities
题意:
给定长度为 N N N 的字符串,找子串中出现次数仅为一次,并且长度最短的子串,如果有多个,输出从左往右第一个出现的子串。
思路:
首先想到暴力算法,先枚举长度,然后模拟求答案
枚举长度会 T L E TLE TLE ,优化时间我们可以选择二分长度
一开始想着 u n o r d e r e d unordered unordered_ m a p < s t r i n g , i n t > map<string,int> map<string,int>,但是由于 s t r i n g string string 的存在,会 M L E MLE MLE,所以我们可以采用哈希优化,把字符串映射成数值,也就变成了 u n o r d e r e d unordered unordered _ m a p < u l l , i n t > map<ull,int> map<ull,int>,这时就不会 M L E MLE MLE 了。
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define epc 1e-5
using namespace std;
const int maxn = 3e5 + 9;
const int mod = 1e9 + 7;
ll n, m, k;
ull Pow[maxn];
ull P = 13331;
ull Hash[maxn];

ull getHash(int l, int r)
{
	return Hash[r] - Hash[l-1] * Pow[r - l + 1];
}

string s;
unordered_map <ull, int> ma;
bool check(int l)
{
		ma.clear();
		for(int i = 0; i + l - 1 < s.size(); ++i)
		{
			ull ans = getHash(i+1, i + l);
			++ma[ans];
		}
		for(auto x : ma)
			if(x.second == 1){
				return 1;
		}
		return 0;
}
void work()
{
	cin >> s;
	n = s.size();
	for(int i = 1; i <= n; ++i)
		Hash[i] = Hash[i-1] * P + s[i-1];
	
	int len = 0;
	int r = s.size(), l = 1;
	while(l < r)
	{
		int mid = (r + l) >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
		//cout << l << endl;
		len = l;
		ma.clear();
		for(int i = 0; i < s.size(); ++i)
		{
			ull ans = getHash(i+1, i + len);
			++ma[ans];
		}
		for(int i = 0; i < s.size(); ++i)
		{
			ull ans = getHash(i+1, i + len);
			string t = "";
			t += s[i];
			int j = i;
			while(j < s.size() && j - i + 1 < len) ++j, t += s[j];
			if(ma[ans] == 1){
				cout << t << endl;return;
			}
		}
}

int main()
{
	ios::sync_with_stdio(0);
	//int TT;cin>>TT;while(TT--)
	Pow[0] = 1;
	for(int i = 1; i <= maxn - 9; ++i)
		Pow[i] = Pow[i-1] * P; 
	work();
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值