Description
给出一个长度为 (n) 的,仅包含小写字母的字符串 (s)。
定义这个字符串以第 (i) 个字符开头的后缀为后缀 (i)(编号从 (1) 开始),每个后缀 (i) 都有一个权值 (w_i),同时定义两个后缀 (i, j (i eq j)) 的贡献为它们的最长公共前缀长度加上它们权值的异或和,也就是 ( ext{LCP}(i, j) + (w_i ext{xor} w_j))。
而你的任务就是,求出这个字符串的所有后缀两两之间贡献的最大值。
数据范围:(1 leq n leq 10^5),(0 leq w_i < n)。
Solution
算法一:SA + 可持久化 0/1 trie
对字符串 (s) 做一遍 ( ext{SA}),再将 ( ext{height}) 数组求出。
为了方便求解,我们求出一个新的后缀权值序列 ({ w'_i }),满足 (w'_i = w_{ ext{SA}_i})。
那么,后缀 ( ext{SA}_i, ext{SA}_j(i < j)) 的贡献即为:
那么现在就是要求出所有满足 (1 leq i < j leq n) 的数对 ((i, j)) 所计算出的上式最大值。
这就是一个经典老题了,考虑分治。
定义分治函数 ( ext{solve}(l, r)),表示计算 (l leq i < j leq r) 时的答案。
取分治中心 ( ext{mid}) 为区间 ((l, r]) 中 ( ext{height}) 值最小的一点。
至于计算 ( ext{mid}) 可以使用 ST 表。
那么现在只需要计算出所有满足 (l leq i < ext{mid} leq j leq r) 的数对 ((i, j)) 的贡献最大值。
随后调用 ( ext{solve}(l, ext{mid} - 1)) 和 ( ext{solve}( ext{mid}, r)) 即可。
对于所有满足 (l leq i < ext{mid} leq j leq r) 的数对 ((i, j)),一定有 (minlimits_{i < k leq j} left{ ext{height}_k
ight} = ext{height}_ ext{mid})。
此时贡献表达式的第一个参数已经确定下来,现在就是要求 (left(w'_i ext{xor} w'_j
ight)) 的最大值。
对于分治中心 ( ext{mid}) 分出来的两个区间 ([l, ext{mid})) 与 ([ ext{mid}, r]),我们选择长度较小的一段区间,穷举该区间里的每一个点,求出该点与另一段区间中的每个点的异或最大值来更新答案,使用可持久化 0/1 trie 维护即可。
我们如果将分治的过程倒过来看,那么 " 穷举较小区间内的所有点 " 其实是一个启发式合并。
每个点至多被枚举了 (log_2 n) 次。故总时间复杂度为 (mathcal{O}(n log^2 n))。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100100;
int n, m = 256;
char s[N];
int temp[N], w[N];
int SA[N], rk[N];
int cnt[N], id[N], oldrk[N * 2], px[N];
bool cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
}
int height[N];
int logx[N];
int f[N][17];
int calc(int l, int r) {
int k = logx[r - l + 1];
if (height[f[l][k]] <= height[f[r - (1 << k) + 1][k]])
return f[l][k];
else
return f[r - (1 << k) + 1][k];
}
int cT, root[N];
struct trie {
int ch[2];
int cnt;
} t[N * 32];
void insert(int &p, int now, int dep, int val) {
p = ++ cT;
t[p] = t[now];
t[p].cnt ++;
if (dep < 0) return;
int v = val >> dep & 1;
insert(t[p].ch[v], t[now].ch[v], dep - 1, val);
}
int ask(int p, int q, int dep, int val) {
if (dep < 0) return 0;
int v = val >> dep & 1;
int cnt = t[t[q].ch[v ^ 1]].cnt - t[t[p].ch[v ^ 1]].cnt;
if (cnt)
return ask(t[p].ch[v ^ 1], t[q].ch[v ^ 1], dep - 1, val) + (1 << dep);
else
return ask(t[p].ch[v], t[q].ch[v], dep - 1, val);
}
int ans;
void solve(int l, int r) {
if (l == r) return;
int mid = calc(l + 1, r);
if (mid - l <= r - mid + 1) {
for (int i = l; i < mid; i ++)
ans = max(ans, height[mid] + ask(root[mid - 1], root[r], 17, w[i]));
} else {
for (int i = mid; i <= r; i ++)
ans = max(ans, height[mid] + ask(root[l - 1], root[mid - 1], 17, w[i]));
}
solve(l, mid - 1), solve(mid, r);
}
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
for (int i = 1; i <= n; i ++)
scanf("%d", &temp[i]);
for (int i = 1; i <= n; i ++) rk[i] = s[i];
for (int i = 1; i <= n; i ++) cnt[rk[i]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) SA[cnt[rk[i]] --] = i;
for (int w = 1, p = 0; w < n; w <<= 1, m = p) {
p = 0;
for (int i = n - w + 1; i <= n; i ++) id[++ p] = i;
for (int i = 1; i <= n; i ++)
if (SA[i] > w) id[++ p] = SA[i] - w;
for (int i = 0; i <= m; i ++) cnt[i] = 0;
for (int i = 1; i <= n; i ++) cnt[px[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) SA[cnt[px[i]] --] = id[i];
p = 0;
for (int i = 1; i <= n; i ++) oldrk[i] = rk[i];
for (int i = 1; i <= n; i ++)
rk[SA[i]] = cmp(SA[i - 1], SA[i], w) ? p : ++ p;
}
for (int i = 1, H = 0; i <= n; i ++) {
if (H) H --;
while (s[i + H] == s[SA[rk[i] - 1] + H]) H ++;
height[rk[i]] = H;
}
logx[0] = -1;
for (int i = 1; i <= n; i ++)
logx[i] = logx[i >> 1] + 1;
for (int i = 1; i <= n; i ++)
f[i][0] = i;
for (int j = 1; j <= 16; j ++)
for (int i = 1; i + (1 << j) - 1 <= n; i ++) {
if (height[f[i][j - 1]] <= height[f[i + (1 << (j - 1))][j - 1]])
f[i][j] = f[i][j - 1];
else
f[i][j] = f[i + (1 << (j - 1))][j - 1];
}
for (int i = 1; i <= n; i ++)
w[i] = temp[SA[i]];
for (int i = 1; i <= n; i ++)
insert(root[i], root[i - 1], 17, w[i]);
solve(1, n);
printf("%d
", ans);
return 0;
}
算法二:SAM + 0/1 trie 合并
(待填 ......)