qoj18234 Compare Suffixes
有一个未知的字符串,你每次可以给出两个下标 \(x,y\) 并得知以 \(x,y\) 开头的后缀的大小关系。
试在 \(n\log\sigma+\sigma\log n\) 次询问内求出该字符串的后缀数组。
其中 \(\sigma=26\) 是字符集大小。
定义 \(s(i)\) 表示第 \(i\) 位的字符,\(S(i)\) 表示以 \(i\) 开头的后缀。
一个暴力的做法是依次插入每一个后缀,每次在当前的 SA 基础上二分得到插入的位置,操作次数 \(n\log n\)。
为了优化它,需要利用 \(\sigma\le26\) 的性质。
我们从平凡的结论开始:
Observation 1:对字符串的每个后缀 \(S(i)\) 排序等价于对二元组 \((s(i),S(i+1))\) 排序。
由这一点出发我们可以进一步得到以下两个结论。
Observation 2:SA 中以每个字符 \(c_i\) 开头的后缀构成连续段。连续段最多有 \(\sigma\) 个。
Observation 3:每个连续段中 \(\text{rk}(\text{sa}(i)+1)\) 递增。
换句话说,如果我们考察 \(f(i)=\text{rk}(\text{sa}(i)+1)\) 的取值,其被至多划分为 \(\sigma\) 个递增的段。且在每个段的结束位置 \(p\) 有 \(s(\text{sa}(p))<s(\text{sa}(p+1))\) 且 \(f(p)>f(p+1)\)。
因此,由于段内 \(f\) 递增,且当我们插入后缀 \(p\) 时,\(\text{rk}(p+1)\) 已知。那么在每个段内 \(p\) 只有一个可能的插入位置。
若 \(p\) 可以被合法地插入某个段,那么可以在段上二分出第一个满足条件的段。这一步至多询问 \(n\log\sigma\) 次。
否则,这意味着 \(p\) 将会创建一个新段,我们直接暴力在所有后缀上二分,由于这种情况最多只会发生 \(\sigma\) 次。这一步总共至多询问 \(\sigma\log n\) 次。
有一个小细节是,开头相同的后缀必在同一段内,但同一段内的后缀开头不一定相同,只要满足 \(f\) 单增即可被合并为一段。具体实现的时候要注意这一点。
#include <algorithm>
#include <iostream>
#include <vector>
const int N = 1007;
int sa[N], rk[N], ext[N];
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int n, m = 1, t = 2; std::cin >> n;
auto compare = [&](int x, int y) -> bool {
std::cout << "query " << x << ' ' << y << "\n" << std::flush;
std::string res; std::cin >> res;
return res == "first";
};
int cmp = compare(n-1, n);
if(cmp) {
sa[1] = n - 1, sa[2] = n;
rk[n - 1] = 1, rk[n] = 2;
} else {
sa[2] = n - 1, sa[1] = n;
rk[n - 1] = 2, rk[n] = 1;
}
auto insert = [&](int p, int q) {
// insert q at pos p
for(int i = t+1; i >= p+1; --i)
if(sa[i-1]) sa[i] = sa[i-1], rk[sa[i]] = i;
sa[p] = q, rk[q] = p;
};
auto locate = [](int id, int p) {
int y = ext[id]-1;
for(int i = ext[id]; i < ext[id+1]; ++i) {
if(rk[p] > rk[sa[i]+1] && i > y)
y = i;
}
return y;
};
auto build = [&]() {
ext[m=1] = 1;
for(int i = 1; i <= t-1; ++i) {
if(rk[sa[i]+1] > rk[sa[i+1]+1]) {
ext[++m] = i+1;
}
}
ext[m+1] = t+1;
};
for(int p = n-2; p >= 1; --p) {
// assume it doesnt craete a new phaseA
build();
int l = 0, r = m+1;
while(r - l > 1) {
int mid = (l + r) >> 1;
int q = locate(mid, p+1);
if(!q || compare(sa[q], p))
l = mid;
else r = mid;
}
int q = locate(l, p+1);
if(q == t || compare(p, sa[q+1])) {
// success
insert(q+1, p);
} else {
// new, just search
int l = 0, r = t+1;
while(r - l > 1) {
int mid = (l + r) >> 1;
if(compare(sa[mid], p))
l = mid;
else r = mid;
}
insert(r, p);
}
++t;
}
std::cout << "answer ";
for(int i = 1; i <= n; ++i)
std::cout << sa[i] << " ";
}
本文来自博客园,作者:CuteNess,转载请注明原文链接:https://www.cnblogs.com/CuteNess/p/22508898

浙公网安备 33010602011771号