EOJ 3424 二分+字符串Hash
题目大意
列出n个字符串,依次从每个字符串中获取一个后缀(不得不取),再依次从上到下连接这些后缀形成新的字符串,问:这样能组合成的字典序最小的字符串是什么。题目在此
思路
题目给的数据范围非常奇妙,单个字符串长度小于50000,保证所有字符串加起来不超过50000。很自然地想到将所有字符串统一起来考虑。
如果我们从最后一位开始枚举添加进答案的字符串前缀,那么时间复杂度约为O(n*(判断的复杂度))。每次枚举一个字符串添加进答案的前缀中,就判断一下新形成的字符串与之前既定的字符串的字典序,如果新形成的串的字典序小,那么将新形成的串当成既定的串。
知识点——字符串Hash比较两字符串字典序
众所周知,字典序比较的传统方法是O(n)比较,如果我们使用传统方法来比较,整个题目的复杂度为O(n2),妥妥地TLE了。那么有什么更加巧妙快速地方法来比较两个串的字典序呢?答案就是字符串Hash。字符串Hash常用与比较两字符串是否相同,但是比较字典序时,它同时也能够派上用处。
比如aaaab
和aabba
两个串,使用传统方法的话,它们的字典序是到第三位出比较结果,因为第三位之前的前缀都是一模一样的。如果我们使用二分法加字符串Hash来查找两个字符串的最长公共前缀长度,字符串Hash本身只需要O(1)的复杂度,那么所需的时间为O(logn)。
AC代码
#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
#define CHAR_NUM 30
#ifdef ACM_LOCAL
const int NUM = 100;
#endif
#ifndef ACM_LOCAL
const int NUM = 500100;
#endif
string strs[NUM];
char ans[NUM];
ull ha[NUM], fac[NUM];
ull seed = 131;
/**
*获取哈希值
*/
ull getStr(int l, int r) {
l--;
if (l == -1)
return ha[r];
else
return ha[r] - ha[l] * fac[r - l];
}
int main() {
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
fac[0] = 1;
for (int i = 1; i < NUM; i++)
fac[i] = fac[i - 1] * seed;
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> strs[i];
int last = -1;
for (int i = n - 1; i >= 0; i--) {
int size = strs[i].size();
last++;
if (last == 0)
ha[last] = strs[i].at(size - 1);
else
ha[last] = ha[last - 1] * seed + strs[i].at(size - 1);
ans[last] = strs[i].at(size - 1);
int tmp = last;
for (int j = size - 2; j >= 0; j--) {
last++;
ha[last] = ha[last - 1] * seed + strs[i].at(j);
ans[last] = strs[i].at(j);
//cmp
int l = 0, r = tmp, mid;
int res = r + 1;
while (l <= r) {
mid = (l + r) / 2;
if (getStr(mid, tmp) == getStr(last - (tmp - mid), last)) {
res = mid;
r = mid - 1;
} else
l = mid + 1;
}
if (ans[last - (tmp - res + 1)] < ans[res - 1])
tmp = last;
}
last = tmp;
}
for (int i = last; i >= 0; i--)
cout << ans[i];
cout << endl;
return 0;
}
总结
字符串Hash是一个非常好用的方法,能够处理大部分的小规模字符串问题。这道题是本人遇到的第一个感觉很厉害的字符串Hash题目,看了网上大佬们的题解才解出了这个题目,颠覆了我对字符串Hash的看法,在此做个笔记。
若有说得不明确或是错误的地方,欢迎评论区指正,谢谢~