0%

[雅礼集训2017Day7]事情的相似度

题目描述

给定前缀结束位置区间,求其两两间 \(LCS\) 的最大值

题解

一个人学 \(SAM\),也需要考虑历史的进程

这道题在一开始有一个口胡的后缀数组和莫队的 \(O(n\sqrt{nlogn})\) 做法,但觉得常数有点大,就放弃了

之后又错误估计了预处理的点对数量,以为是 \(O(n^2)\),然后就看了题解,发现是 \(O(nlogn)\)

任意两前缀的 \(LCS\) 一定是相应 \(np\) 节点在 \(Parent\) 树上的 \(LCA\)\(maxlen\),我们预处理一些点对即可,具体来说,两个子节点合并到父节点时,我们找最接近的合并一定不会差,这样和启发式合并复杂度一致 之后把操作离线,用后缀树状数组搞一下扫描线即可

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <set>
using namespace std;
inline int read(int f = 1, int x = 0, char ch = ' ')
{
while(!isdigit(ch = getchar())) if(ch == '-') f = -1;
while(isdigit(ch)) x = x*10+ch-'0', ch = getchar();
return f*x;
}
const int N = 2e5+5;
int last = 1, tot = 1, ch[N][2], fa[N], len[N];
int m, p[N], tax[N];
set<int> t[N];
char s[N];
void insert(int c)
{
int cur = ++tot, pre = last; last = cur;
len[cur] = len[pre]+1; t[cur].insert(len[cur]);
while(pre&&!ch[pre][c]) ch[pre][c] = cur, pre = fa[pre];
if(!pre) return void(fa[cur] = 1);
int x = ch[pre][c];
if(len[pre]+1 == len[x]) return void(fa[cur] = x);
int y = ++tot; fa[y] = fa[x]; fa[x] = fa[cur] = y; len[y] = len[pre]+1;
memcpy(ch[y], ch[x], sizeof(ch[x]));
while(pre&&ch[pre][c] == x) ch[pre][c] = y, pre = fa[pre];
}
int n, q;
int ans[N];
struct Tup
{
int w[3];
Tup(int x = 0, int y = 0, int z = 0){ w[0] = x; w[1] = y; w[2] = z; }
int operator [] (const int x) { return w[x]; }
bool operator < (const Tup &_) const
{
for(int i = 0; i < 3; ++i)
if(w[i] != _.w[i]) return w[i] < _.w[i];
return true;
}
}tup[N<<6], par[N];
int val[N];
void change(int x, int d) { for( ; x; x -= x&-x) val[x] = max(val[x], d); }
int query(int x) { int ret = 0; for( ; x <= n; x += x&-x) ret = max(ret, val[x]); return ret; }
int main()
{
scanf("%d%d", &n, &q); scanf("%s", s+1);
for(int i = 1; i <= n; ++i) insert(s[i]-'0');
for(int i = 1; i <= tot; ++i) ++tax[len[i]];
for(int i = 1; i <= tot; ++i) tax[i] += tax[i-1];
for(int i = 1; i <= tot; ++i) p[tax[len[i]]--] = i;
for(int i = tot; i > 1; --i)
{
int x = p[i]; if(t[x].size() > t[fa[x]].size()) swap(t[x], t[fa[x]]);
set<int>::iterator it, a, b, c;
for(it = t[x].begin(); it != t[x].end(); ++it)
{
t[fa[x]].insert(*it); a = b = c = t[fa[x]].find(*it); ++c;
if(a != t[fa[x]].begin()) --a, tup[++m] = Tup(*b, *a, len[fa[x]]);
if(c != t[fa[x]].end()) tup[++m] = Tup(*c, *b, len[fa[x]]);
t[fa[x]].erase(*it);
}
for(it = t[x].begin(); it != t[x].end(); ++it) t[fa[x]].insert(*it);
}
sort(tup+1, tup+1+m);
for(int i = 1; i <= q; ++i)
{
int l = read(), r = read();
par[i] = Tup(r, l, i);
}
sort(par+1, par+1+q);
for(int i = 1, ptr = 1; i <= q; ++i)
{
while(ptr <= m&&tup[ptr][0] <= par[i][0]) change(tup[ptr][1], tup[ptr][2]), ++ptr;
ans[par[i][2]] = query(par[i][1]);
}
for(int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
return 0;
}