= last column of the sorted rotations of ( a unique smallest sentinel), computable from the suffix array: . It clusters equal characters (good for RLE + move-to-front: bzip2), is invertible by the LF-mapping, and with rank queries becomes the FM-index: count occurrences of by backward search in (DNA aligners). Uses sais from Suffix Array. CSES 1113 String Transform is inverseBwt.
| Use | How | Cost |
|---|---|---|
| transform | suffix array of | with SA-IS |
| inverse | LF mapping: stable sort of positions by character | or counting |
| count occurrences of (FM-index) | backward search with and | with an table (wavelet tree: , bits) |
| locate occurrences | SA entries of the final interval (sampled SA in practice) | |
| compressibility | number of BWT runs ; LZ77 factors | |
| compression pipeline | BWT move-to-front RLE / entropy coding |
string bwt(const string& s) { // s must not contain '#'; result has exactly one '#'
vector<int> v(sz(s) + 1, 0); for (int i = 0; i < sz(s); i++) v[i] = (unsigned char)s[i] + 1;
vector<int> sa = sais(v, 257); int n = sz(v);
string t = s + '#', res(n, ' ');
for (int i = 0; i < n; i++) res[i] = t[(sa[i] + n - 1) % n];
return res;
}
string inverseBwt(const string& b) { // LF mapping
int n = sz(b); vector<int> nxt(n); iota(all(nxt), 0);
auto key = [](char c) { return c == '#' ? -1 : (int)(unsigned char)c; };
stable_sort(all(nxt), [&](int x, int y) { return key(b[x]) < key(b[y]); }); // row r's first char is b[nxt[r]]
string res; int i = nxt[0];
for (int k = 0; k + 1 < n; k++) { i = nxt[i]; res += b[i]; }
return res;
}
struct FMIndex { // lowercase text
string b; vector<int> sa, C; vector<array<int, 27>> occ; // occ[i][c] = #c in b[0, i), c = 0 for '#'
FMIndex(const string& s) {
vector<int> v(sz(s) + 1, 0); for (int i = 0; i < sz(s); i++) v[i] = s[i] - 'a' + 1;
sa = sais(v, 27); int n = sz(v); b.assign(n, ' ');
for (int i = 0; i < n; i++) { int j = (sa[i] + n - 1) % n; b[i] = j == n - 1 ? '#' : s[j]; }
occ.assign(n + 1, {}); for (int i = 0; i < n; i++) { occ[i + 1] = occ[i]; occ[i + 1][b[i] == '#' ? 0 : b[i] - 'a' + 1]++; }
C.assign(28, 0); for (int c = 0; c < 27; c++) C[c + 1] = C[c] + occ[n][c];
}
pair<int, int> range(const string& p) const { // SA rows [lo, hi) whose suffixes start with p
int lo = 0, hi = sz(b);
for (int i = sz(p) - 1; i >= 0 && lo < hi; i--) { int c = p[i] - 'a' + 1; lo = C[c] + occ[lo][c]; hi = C[c] + occ[hi][c]; }
return {lo, hi};
}
int count(const string& p) const { auto [lo, hi] = range(p); return max(0, hi - lo); }
vector<int> locate(const string& p) const { auto [lo, hi] = range(p); vector<int> r; for (int i = lo; i < hi; i++) r.push_back(sa[i]); sort(all(r)); return r; }
};
vector<int> moveToFront(const string& s) { vector<int> order(256); iota(all(order), 0); vector<int> out; for (unsigned char c : s) { int i = find(all(order), (int)c) - order.begin(); out.push_back(i); order.erase(order.begin() + i); order.insert(order.begin(), c); } return out; }
string inverseMoveToFront(const vector<int>& v) { vector<int> order(256); iota(all(order), 0); string s; for (int i : v) { int c = order[i]; s += char(c); order.erase(order.begin() + i); order.insert(order.begin(), c); } return s; }Proof: the BWT, its inverse and backward search
From the SA. The rotations of sorted lexicographically are in suffix order, because is unique and smallest (comparison stops at the sentinel); the last character of the rotation starting at is . LF-mapping. The -th occurrence of character in the last column (BWT) and the -th occurrence of in the first column are the same text position: rows ending in , rotated by one, start with and keep their relative order (it is decided by what follows ). So the stable sort of row indices by BWT character maps each row of the first column to the row whose last character it is; the code’s
nxt[r]is the row of the next rotation, and walking from the row of spells . Backward search. If the rows prefixed by form , the rows prefixed by are : counts rows starting with a smaller character, and among rows starting with the order is that of the following suffixes, i.e. of the rows in whose BWT character is , by the LF argument. Move-to-front is a bijection (the decoder maintains the same list). Checked: round trips, FM counts againstfind, locations against brute force.
Related notes
- Suffix Array
- Range Order Statistics (wavelet trees for rank queries)
- Lyndon Words (bijective BWT)