返回全部题目

BUC3399K 下笔成章

题目描述

小F 有一个字符串ss,她想知道是否存在这样一个子串tttt 既是ss 的前缀,又是ss 的后缀,同时在字符串中还出现过一次(在除前后缀以外出现,可能和前后缀有部分重合)。
假如存在这样的子串tt,请输出最长长度的tt,否则输出 I'll write one for you

输入

小写字母组成的子串ss
ss 长度在[1,1e6][1,1e6]

输出

存在则输出tt,否则输出 I'll write one for you

样例输入

1
fixprefixixffix

样例输出

1
fix

比如我现在有一串字符串 fixprefixixffix,因为都是小写,我们可以将其转换为0250\sim25 的数字,即 5,8,23,15,17,4,5,8,23,8,23,5,5,8,23

勘误:尽量不要用00,建议改为1261\sim26


首先复习一下,如果我要多次查询连续几项的和,如第131\sim3 项元素之和,以及第797\sim9 项元素之和,应该怎样操作?

聪明的你知道,先预处理前缀和,得到 5,13,36,51,68,72,77,85,108,116,139,144,149,157,180,那么 sum[3]-sum[0]=36, sum[9]-sum[6]=36 即是结果,前缀和处理可以大大降低时间复杂度。

如果我问,哪些连续三项的和等于 36,你一定也能很快得出结果,是13, 79, 1012, 13151\sim3,~7\sim9,~10\sim12,~13\sim15 这些,

我们发现,这里面有些恰好是 fix,但有些不是,比如 ixf,利用前缀和搜索是不可行的,因为就算这几项的和相同,字母也不一定相同。


有什么办法解决呢?

就拿阿拉伯数字举例,我要知道1908219082 中哪个子串和1919 相同,你肯定不会作1+9=8+21+9=8+2 这样的比较就认为19=8219=82 吧,因为19=1×10+98×10+2=8219=1\times10+9\neq8\times10+2=82,而19=1×10+9=1×10+9=1919=1\times10+9=1\times10+9=19

这启发我们,可以对高位乘上一个数字,比如2929将这一串数看做29\bold{29} 进制,那么比较就轻而易举了。(一般选用1311311333113331

具体地说,第131\sim3fix 字符串的 新概念和5×292+8×29+23=44605\times29^2+8\times29+23=4460,而第101210\sim12ixf 字符串的 新概念和8×292+23×29+5=74008\times29^2+23\times29+5=7400,它们不相同,所以这俩字符串也不相同。

这就是核心思想,可以找一些字符串感受感受\sim


当然,按照前缀和的思想,我们也需要先预处理 新概念前缀和 ,再进行查询操作。

预处理的时候,我们对第ii 项乘上29i29^i,再求前缀和。比如刚才那个字符串,先乘,得到5×29, 8×292, 23×293, 15×294, 17×2955\times29,~8\times29^2,~23\times29^3,~15\times29^4,~17\times29^5\dots,即145,6728,560947,10609215,348689533145,6728,560947,10609215,348689533,计算前缀和就是145,6873,567820,11177035,359866568145,6873,567820,11177035,359866568

如果我要找8,23,158,23,15,计算一下它对于的 新概念和 ,是8×29+23×292+15×293=3854108\times29+23\times29^2+15\times29^3=385410,一一遍历比较:131\sim3 项,5678200385410567820-0\ne385410,不是;242\sim4 项,(11177035145)/29=385410(11177035-145)/29=385410,是的;353\sim5 项,(3598665686873)/292=427895385410(359866568-6873)/29^2=427895\ne385410,不是。(至于为什么要除以29229^2,简单来说是预处理的时候多乘了这么多倍,所以还原时要除回来,可以把数字带进去验证一下)

就这样在Θ(n)\Theta(n) 的时间内完成了遍历。


下面上模板:

定义变量:

1
2
const int H = 29; // 进制数
unsigned long long sum[M], p[M];

预处理:

1
2
3
4
5
6
7
void init() {
p[0] = 1;
for (int j = 1; j <= m; j++) {
p[j] = p[j - 1] * H; // 存储29的多少次方
sum[j] = sum[j - 1] * H + (s[j] - 'a'); // 新概念前缀和
}
}

查询:

1
2
3
4
5
6
7
8
int get(int i, int j) {
return sum[j] - sum[i - 1] * p[j - i + 1];
}
int main() {
if (get(a) == get(l,r)) {
// a串和[l,r]子串相同
}
}

好啦,现在你可以尝试完成本文开头那道题目了\sim


AC~code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#define ull unsigned long long
#define Code int main() { int T = 1;
#define Codes int main() { int T; scanf("%d\n", &T);
#define by while (T--) eachT();
#define HTLDL return 0;
#define HappyNewYear }

const int M = 1e6 + 7;
const int H = 29;
ull sum[M], p[M];
std::string s;

void init(int m) {
p[0] = 1;
for (int j = 1; j <= m; j++) {
p[j] = p[j - 1] * H;
sum[j] = sum[j - 1] * H + s[j] - 'a';
}
}

ull get(int i, int j) {
return sum[j] - sum[i - 1] * p[j - i + 1];
}

void eachT() {
std::cin >> s;
int m = s.size();
s = " " + s;
init(m);
for (int len = m - 1; len >= 1; --len) {
ull h1 = get(1, len), h2 = get(m - len + 1, m);
if (h1 != h2) continue;
for (int i = 2; i <= m - len; ++i) {
h2 = get(i, i + len - 1);
if (h1 == h2) {
for (int j = 1; j <= len; ++j)
std::cout << s[j];
return;
}
}
}
std::cout << "I'll write one for you";
}

Code by HTLDL
HappyNewYear

最后附赠一个练习题:

题目描述

就像 小F 有幸运数字一样,太阳也有喜欢的字符串AA
AA 的首字母移到末尾,执行任意次,可以得到所有和AA 结构相同的字符串。
小F 喜欢赞美太阳,同时她还有nn 个字符串B1B_1B2B_2B3B_3……她想知道对于自己的每一个字符串,有多少个子串和AA 结构相同。

输入

第一行为字符串AA 串长1e6\leqslant1e6
第二行为n1000n\leqslant1000
随后nn 行为B1BnB_1\sim B_n,单个串长不超过1e61e6,总串长不超过1e71e7

输出

输出nn 行,每个串有多少子串与AA 结构相同

样例输入

1
2
3
4
abab
2
abababab
ababcbaba

样例输出

1
2
5
2

作者在一小时前刚初步了解了哈希,并写下了这篇笔记,如有错误恳请各位指出。

应该比较容易理解吧、、、