hash进阶

目录:

1.进制hash

 2.查询子串的Hash值

3.用hash求最长回文子串 / 回文子串数
 1.进制hash
 作用:
 把字符赋予进制和模数,将每一个字符串映射为一个小于模数数字。
 思路:
 首先设一个进制数base,并设一个模数mod,
 进制哈希就是把一个串转化为一个值,这个值是base进制的,
 储存在哈希表中,注意一下在存入的时候取模一下即可。
 解决hash冲突:
 (1)用一个大质数当模数
 (2)双模数哈希:
 (通过设置两个不同的哈希方式,对于一个字符串,
 当且仅当两个哈希值都相同时才判定相同。)
 对一个hash值用两个不同的质数进行两次mod操作,
 然后最后用一对数<hash1[n],hash2[n]>来表示一个字符串的哈希值,
 这样的一对数的重复几率加上选择较大的质数,冲突率几乎为0。
 (3)自然溢出法:
 对于自然溢出方法,我们定义Base 而MOD对于自然溢出方法,
 就是 unsigned long long 整数的自然溢出
 这种方法是利用数据结构unsigned long long的范围自然溢出:
 即当存储的数据大于unsigned long long的存储范围时,
 会自动mod 2^64−1,就不用mod其他质数来保证唯一性了。
 代码实现:

#define base 233317
 #define inf 212370440130137957(LL)
 ull mod = inf;
 ull hasha(char s[]) {
    ll ans = 0, len = strlen(s);
    for (ll i = 0; i < len; i++) {
        ans = (ans * base + (ull)s[i]) % mod;
        //枚举该字符串的每一位,与base相乘,转化为base进制,
        //加(ull)是为了防止爆栈搞出一个负数,(ull)是无符号的,
         //但其实加了一个ull是可以不用mod的,加个mod更保险
        //然而加了mod会很玄学,莫名比不加mod慢
    }
    return ans;
}


# 【模板】字符串哈希
## 题目描述
如题,给定N个字符串(第i个字符串长度为M_i,字符串内包含数字、大小写字母,大小写敏感),请求出 $N$ 个字符串中共有多少个不同的字符串。
** 友情提醒:如果真的想好好练习哈希的话,请自觉。**
## 输入格式
第一行包含一个整数 $N$,为字符串的个数。
接下来 $N$ 行每行包含一个字符串,为所提供的字符串。
## 输出格式
输出包含一行,包含一个整数,为不同的字符串个数。
## 样例 #1
### 样例输入 #1
5
abc
aaaa
abc
abcc
12345
### 样例输出 #1
4

#include<iostream>
#include<algorithm>
const int Maxn = 100010;
typedef unsigned long long ull ;
typedef long long ll;
using namespace std;
char s[Maxn];
ull key[Maxn];          //建立unsigned long long 的数组防止溢出
const ull base = 233317;
int sum=0;
ull Hash(string s) {
	ull ans = 0; ll len = s.size();
	for (ll i = 0; i < len; i++) {          //自然溢出法
		ans = (ans * base) + (ull)s[i];
	}
	return ans;      //得到hash地址的解
}
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> s;
		key[i]=Hash(s);
	}
	sort(key, key + n);
	for (int i = 0; i < n; i++) {
		if (key[i] != key[i + 1])   //比较得到不同值
			sum++;
	}
	cout << sum << endl;
	return 0;
}


 2.查询子串的Hash值
设一个字符串的前缀hash值记为h[i],
我们hash时使用的进制数为base,那么显然 h[i] = h[i−1]∗base + s[i]
p[i]表示base的i次方,那么我们可以通过这种方式O(1)得到一个子串的hash值
(设这个子串为s[l]...s[r])
代码实现:

typedef unsigned long long ull;
ull get_hash(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}


原因:
进行进制哈希的过程本质上就是把原先得到的哈希值在base进制上强行左移一位,
 然后放进去当前的这个字符。
现在的目的是,取出l到r这段子串的hash值,也就是说,
h[l−1]这一段是没有用的,我们把在h[r]这一位上,
h[l−1]这堆字符串的hash值做的左移运算全部还原给h[l−1],
就可以知道h[l−1]在h[r]中的hash值,那么减去即可。
[Baltic OI 2014 Day1] Three Friends
题目描述
有一个字符串 S,对他进行操作:
1.将 S 复制为两份,存在字符串 T 中
2.在 T 的某一位置上插入一个字符,得到字符串 U
现在给定 U,求 S。
如果不能通过上述的步骤从 S 推到 U,输出 NOT POSSIBLE。
如果从 U 得到的 S 不是唯一的,输出 NOT UNIQUE。
否则,输出一个字符串 S。
 思路:
现在有一个字符串s,每次询问它的一个子串删除其中一个字符后的hash值
(删除的字符时给定的)
类比上面的做法,我们可以先O(1)得到区间[l, x−1]和区间[x + 1, r]的hash值,
那么现在要做的事情就是把这两段拼起来了,由于使用的是进制hash,
强行将前面的区间强行左移r−x位(这么看可能会好理解一点r−(x + 1) + 1)就好。

typedef unsigned long long ull;
ull get_hash(int l, int r) {
	return h[r] - h[l - 1] * p[r - l + 1];
}
ull get_s(int l, int r, int x) {
	return get_hash(l, x - 1) * p[r - x] + get_hash(x + 1, r);
}


整体实现:

#include<iostream>
#include<map>
const int MAXN = 2000005;
typedef unsigned long long  ull;
using namespace std;
long long n, sum = 0, ans, x;
ull  Hash[MAXN], s[MAXN];
string u;
map<long long, bool> flag;
void init()
{
	s[0] = 1;
	for (long long i = 1; i < n; i++)
	{
		s[i] = s[i - 1] * 31;
		//*31是为了减少hash冲突
	}
}
void HASH(string u) {
	for (long long i = 0; i < n; i++)
	{
		Hash[i + 1] = Hash[i] * 31 + (ull)(u[i] - 'A' + 1);
	}
}
int main()
{
	cin >> n;
		cin >> u;
	if (!(n % 2))
	{
		printf("NOT POSSIBLE");
		return 0;
	}
	//因为S翻倍后要插入一个字符,也就是奇数,
   //所以如果是偶数就输出NOT POSSIBLE,并结束
	init();
	HASH(u);
	x = n >> 1;
	for (long long i = 1; i <= x; i++)
	{
		if (Hash[x + 1] - Hash[i] * s[x + 1 - i] + Hash[i - 1] * s[x + 1 - i] == Hash[n] - Hash[x + 1] * s[x] && !flag[Hash[n] - Hash[x + 1] * s[x]])
		{
			sum++;
			//找到了插入的字符也就自然有一个答案 
			ans = i;
			flag[Hash[n] - Hash[x + 1] * s[x]] = 1;
			//为了后面不再重复找到,flag便置为1,后面便进不了循环
		}
	}
	//插入的字符在前面一半之间,则S(也就是答案)就在后面一半,
	//就在前面枚举字符的位置,然后根据 hash值来判断是否相等; 
	if (Hash[x] == Hash[n] - Hash[x + 1] * s[x] && !flag[Hash[x]])
	{
		sum++;
		ans = x + 1;
		flag[Hash[x]] = 1;
	}
	for (long long i = x + 2; i <= n; i++)
	{
		if (Hash[n] - Hash[i] * s[n - i] + (Hash[i - 1] - Hash[x] * s[i - 1 - x]) * s[n - i] == Hash[x] && !flag[Hash[x]])
		{
			sum++;
			ans = i;
			flag[Hash[x]] = 1;
		}
	}
	//在后面则相反 
	if (!sum)
	{
		cout << "NOT POSSIBLE" << endl;
	}
	//没有答案输出NOT POSSIBLE
	else
	{
		if (sum > 1)
		{
			cout << "NOT UNIQUE" << endl;
		}
		//如果答案不唯一,输出NOT UNIQUE 
		else
		{
			if (ans <= x + 1)
			{
				for (long long i = x + 2; i <= n; i++)
				{
					cout<< u[i - 1];
				}
			}
			//如果ans比U的一半少就输出后面一半 
			else
			{
				for (long long i = 0; i < x; i++)
				{
					cout<<u[i];
				}
			}
			//否则相反 
		}
	}
	return 0;
}


3.用hash求最长回文子串 / 回文子串数
回文子串是具有单调性的——二分解决
 

#include<iostream>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
#define N 10100
#define base 13131
char s[N];
ull h1[N], p[N], h2[N], ans = 0;
int n;
ull gh1(int l, int r) { return h1[r] - h1[l - 1] * p[r - l + 1]; }
ull gh2(int l, int r) { return h2[l] - h2[r + 1] * p[r - l + 1]; }
ull query1(int x) { //奇 
    int l = 1, r = min(x, n - x);
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (gh1(x - mid, x + mid) == gh2(x - mid, x + mid)) l = mid + 1;
        else r = mid - 1;
    }
    return r;
}
ull query2(int x) { //偶 
    int l = 1, r = min(x, n - x);
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (gh1(x - mid + 1, x + mid) == gh2(x - mid + 1, x + mid)) l = mid + 1;
        else r = mid - 1;
    }
    return r;
}
int main() {
    scanf("%s", s + 1); p[0] = 1;
    n = strlen(s + 1);
    for (int i = 1; i <= n; ++i) {
        h1[i] = h1[i - 1] * base + s[i];
        p[i] = p[i - 1] * base;
    }
    for (int i = n; i; i--) h2[i] = h2[i + 1] * base + s[i];
    for (int i = 1; i < n; ++i) {
        ans += query1(i) + query2(i);
    }
    printf("%llu\n", ans + n);
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《算法竞赛指南》是一本级别的书籍,不适合初学者阅读。根据引用中的描述,每一章都会总结书中的难点知识,并附上例题和习题。从引用的目录可以看出,《算法竞赛指南》包含了基本算法、位运算、递推与递归、前缀和与差分、二分、排序、倍增、贪心等内容,还包括了基本数据结构如栈、队列、链表、Hash、字符串、Trie、二叉堆等。此外,书中还讲解了动态规划的各种子领域,如线性dp、背包、区间dp、树形dp、状态压缩dp等。对于想要深入学习算法竞赛的读者来说,《算法竞赛指南》是一本很好的参考书籍。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【算法竞赛指南】学习笔记](https://blog.csdn.net/cpp_juruo/article/details/122520206)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [算法竞赛指南总结(一)](https://blog.csdn.net/weixin_64393298/article/details/124234703)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值