\(\text{LOJ \#6041. 「雅礼集训 2017 Day7」事情的相似度 题解}\)
解法一
由 parent 树的性质得到,前缀 \(s_i,s_j\) 的最长公共后缀实质上就是 \(i,j\) 在 SAM 中的 \(\operatorname{LCA}\) 在 SAM 中的 \(\operatorname{len}\)。让我们考虑如何处理 \((l,r)\) 区间内的询问。直接考虑求 \((l,r)\) 的贡献是困难的,不妨转变思路,求每个点对于子树内的贡献。对于一个点 \(x\),我们可以容易地用启发式合并来合并出子树内所有的节点。考虑加入子树 \(y\) 的贡献后怎么做:不难发现对于一个 \(a\),我们只需要统计离 \(a\) 最近的 \(p,q(p\le a,a\le q)\)。这样我们在合并的时候顺便用 std::set
可以求出。由启发式合并的复杂度可以知道,这样做的总复杂度是 \(O(n\log^2n)\)。 然后我们得到了 \(O(n\log n)\) 个贡献三元组 \((l,r,w)\) 和 \(m\) 个询问 \((l,r)\),实际上是一个二维偏序问题,排序后树状数组维护即可。总的时间复杂度是 \(O(n\log^2n)\) 的。
代码:
#include <bits/stdc++.h>
#define N 2000005
#define M 2
using namespace std;
int n, m;
char s[N];
int tot = 1, lst = 1;
struct SAM {
int len, fa;
int s[M];
} sam[N];
set<int>st[N];
void insert(int c, int x) {
int p = lst, np = lst = ++tot;
sam[np].len = sam[p].len + 1;
for (; !sam[p].s[c]; p = sam[p].fa) sam[p].s[c] = np;
if (!p) sam[np].fa = 1;
else {
int q = sam[p].s[c];
if (sam[q].len == sam[p].len + 1) sam[np].fa = q;
else {
int nq = ++tot;
sam[nq] = sam[q], sam[nq].len = sam[p].len + 1;
sam[np].fa = sam[q].fa = nq;
for (; sam[p].s[c] == q; p = sam[p].fa) sam[p].s[c] = nq;
}
}
st[lst].insert(x);
}
vector<int>v[N];
struct node {
int r, vl;
};
vector<node>upd[N], que[N];
void dfs(int x) {
for (auto y : v[x]) {
dfs(y);
if (st[x].size() < st[y].size()) swap(st[x], st[y]);
st[x].insert(-1e9), st[x].insert(1e9);
for (auto i : st[y]) {
auto it = --st[x].upper_bound(i);
if ((*it) != -1e9) upd[*it].push_back({i, sam[x].len});
it = st[x].lower_bound(i);
if ((*it) != 1e9) upd[i].push_back({*it, sam[x].len});
}
st[x].erase(-1e9), st[x].erase(1e9);
st[x].insert(st[y].begin(), st[y].end());
st[y].clear();
}
}
int lbt(int x) {
return x & -x;
}
int tr[N];
void add(int x, int vl) {
while (x <= n) tr[x] = max(tr[x], vl), x += lbt(x);
}
int ask(int x) {
int ans = 0;
while (x) ans = max(tr[x], ans), x -= lbt(x);
return ans;
}
int ans[N];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
cin >> s;
for (int i = 0; i < n; i++) insert(s[i] - '0', i + 1);
for (int i = 2; i <= tot; i++) v[sam[i].fa].push_back(i);
dfs(1);
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
que[l].push_back({r, i});
}
for (int i = n; i >= 1; i--) {
for (auto p : upd[i]) add(p.r, p.vl);
for (auto p : que[i]) ans[p.vl] = ask(p.r);
}
for (int i = 1; i <= m; i++) cout << ans[i] << "\n";
return 0;
}
解法二
实际上本题是信息搜集题,我将给出符合题目描述的图片,相信大家能推断出本题真正的答案。
标签:insert,sam,fa,LOJ,题解,len,st,int,Day7 From: https://www.cnblogs.com/Rock-N-Roll/p/18672755