【ECNU OJ】EOJ 3424 以及 浅谈字符串哈希

"哈希——优雅的暴力”


终于在学校oj的millioaire刷到第一页了,开始补题。


哈希能做什么?

哈希能够帮你把一段字符串转换成数字,从而当预处理得当,可以在O(1)的时候比较两个字符串是否相同(仅仅是相同。

通常以下方法构造:

       

观察上述公式(我的latex用的巨垃圾):

    

定义Hash(i,L) = H(i) - H(i+L)x^L

展开后等于:Hash(i,L) = s[i+L-1]x^(L-1) + ...... s[i+1]x + s[i]

该表达式只和s[i] ~ s[i+L-1]有关,也就是只与字符串的起始位置和长度有关。

注意到Hash值可能会很大导致溢出。所以我们一般用unsigned long long 存储,因为这样的话哪怕溢出了就就直接二等于对2^64取模了(还记得CSAPP吗)。


但不是只能判断是否相等吗?还能有什么大用处?

1.最长公共子串。

什么?最长公共字串怎么做?难道枚举所有大小的子串塞进set<>里然后找最大的值?

没错,还真的就是这么做的。就像开头所说的,哈希就是一种优雅的暴力,的确如此。

时间复杂度O(L1^2+L2^2)

二分长度可以快些O(L1lgL1 + L2lgL2)


2.最长公共前缀/后缀

......?

 没错,还是暴力,就是把所有前后缀找出来看看能否凑成

并且依然可以通过二分优化


3.最小循环串(最小表示法)

比如;bbaa ——> aabb  azaadfa ——>aadfaaz

不断取两个位置,然后二分,判断字典序。


好的,上正菜:

3424. Suffix

题目大意:给你一组字符串,每个字符串取一个后缀(必须取至少一个),然后求按照次序连起来后字典序最小。


解法:1、如果决心用暴力的方法,就不要搞那些花里胡哨的东西

           2、从最后一个往前找,找到最小字典序,往前贴上去,然后再找,再贴。

            3、但是如果真的通过"贴",万一出现前面全一个后面超大数据的时候可能会导致(n^2)的情况出现【不信可以用后缀数组做一遍,一半多就TLE】

            4、于是我们考虑倒过来一个一个插入。构造一个答案数组,首先第一步:至少有一个要加进去。然后从尾到前扫一遍当前这个字符串。一旦字典序比之前小,就保存,然后继续尝试。这里有个技巧,无论怎么样,都把当前字符加答案里,因为是倒序的,哪怕这些字符你不会用到,在以后的循环也只会被替换掉或舍弃掉。


#include <bits/stdc++.h>
using namespace std;
typedef long long int big_int; 
const int maxn = 5e5 + 100;
const int seed = 131;
string str[maxn];
char ans[maxn];
char cache[maxn];
int n;


big_int H[maxn];
big_int sum[maxn];

bool possible(int now , int pre){
	int l = 1 , r = pre;
	while(l <= r)
	{
		int mid = (l + r) >> 1;
		if(sum[now] - sum[now - mid] * H[mid] == sum[pre] - sum[pre - mid] * H[mid])
			l = mid + 1;
		else
			r = mid - 1;
	}
	if(l == pre + 1)
		return false;
	return ans[now - r] < ans[pre - r];
}


void init_hash();
int main(){
	init_hash();
	cin >> n;
	for (int i = 1 ; i <= n ; ++i)
	{
		scanf("%s",cache);
		str[i] = string(cache);
	}
	
	int end_pos = 0;
	
	for (int i = n ; i >= 1 ; --i)
	{
		int len = str[i].size();
		ans[++end_pos] = str[i][len - 1];
		sum[end_pos] = sum[end_pos - 1] * seed  + (str[i][len - 1] - 26);
		int step = 1;
		int t = end_pos;
		for (int j = len - 2 ;  j >= 0 ; --j)
		{
			ans[end_pos + step] = str[i][j];
			sum[end_pos + step] = sum[end_pos + step - 1] * seed  + (str[i][j] - 26);
			if(possible(end_pos + step,t))
				t = end_pos + step;
			step++;
		}
		end_pos = t;
	}
	for (int i = end_pos ; i >= 1 ; --i)
		printf("%c",ans[i]);
	cout << endl;
	
	return 0;
}

void init_hash(){
	H[0] = 1;
	for (int i = 1 ; i <= maxn; ++i)
		H[i] = H[i-1] * seed; 
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值