"哈希——优雅的暴力”
终于在学校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;
}