Description
对于一个给定长度为 (n) 的字符串,求它的第 (k) 小子串。
输入给定一个正整数 (T),若 (T = 0) 则表示不同位置的相同子串算作一个,(T = 1) 则表示不同位置的相同子串算作多个。
如果无解输出 -1
。
Hint
- (1le nle 5 imes 10^5)
- (T in {0, 1})
- (1le kle 10^9)
Solution
(T = 0) 的情况比较简单,参见:「SPOJ SUBLEX」Lexicographical Substring Search
(T = 1) 就相对麻烦得多了,接下来我们一起讨论。
我们设 ( ext{cnt}(x)) 为结点 (x) 的 点权。当 (T = 0) 时,( ext{cnt}(x) = 1);(T = 1) 时,( ext{cnt}(x)) 就是状态 (x) 所对应的 ( ext{end-pos}) 集合大小,因为其对应的每个串都有 (| ext{end-pos}(i)|) 个。
( ext{end-pos}) 集的大小怎么求呢?这个简单,直接拓扑,倒序向 ( ext{link}) 方向加就行。
但光有 ( ext{cnt}) 还不够,我们还需引入一个 ( ext{sum})。( ext{sum}(x)) 表示 经过 (x) 的子串数量。
然后就是 (k- ext{th}) 问题的套路:假如当前走到点 (x),从字典序最小的开始,以 ( exttt{a} ightarrow exttt{z}) 的顺序扫去。当扫到字符 (c) 时,设 (y = delta(x, c)),那么:
- ( ext{sum}(y) ge k):(x) 应该走这条转移,更新答案。(kleftarrow k - ext{cnt}(y)),因为还要减去自己的权值(贡献)。结束对 (c) 的循环。
- ( ext{otherwise}):(kleftarrow k - ext{cnt}(y)),将 (c) 设成下一个字符,尝试下一个转移。
时间复杂度(map
版):(O(nlog Sigma + | ext{ans}|)),空间复杂度:(O(n))。
Code
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : LOJ #2102 TJOI2015 弦论
*/
#include <iostream>
#include <map>
#include <string>
using namespace std;
const int N = 5e5 + 5;
namespace SAM {
const int T = N << 1;
struct Node {
map<char, int> ch;
int link, len;
int sum, cnt;
} t[T];
int last, total;
void extend(char c) {
int p = last, np = last = ++total;
t[np].len = t[p].len + 1;
for (; p && !t[p].ch.count(c); p = t[p].link)
t[p].ch[c] = np;
if (!p) {
t[np].link = 1;
} else {
int q = t[p].ch[c];
if (t[q].len == t[p].len + 1) {
t[np].link = q;
} else {
int nq = ++total;
t[nq].ch = t[q].ch, t[nq].link = t[q].link;
t[nq].len = t[p].len + 1;
t[np].link = t[q].link = nq;
for (; p && t[p].ch[c] == q; p = t[p].link)
t[p].ch[c] = nq;
}
}
t[np].cnt = 1;
}
void init(string& s) {
total = last = 1;
for (string::iterator it = s.begin(); it != s.end(); it++)
extend(*it);
}
int b[T], c[T];
void topo_sort() {
for (register int i = 1; i <= total; i++) ++c[t[i].len];
for (register int i = 1; i <= total; i++) c[i] += c[i - 1];
for (register int i = 1; i <= total; i++) b[c[t[i].len]--] = i;
}
void compute(int type) {
for (register int i = total; i; i--) {
int x = b[i];
if (type == 0) t[x].cnt = 1;
else t[t[x].link].cnt += t[x].cnt;
}
t[1].cnt = 0;
for (register int i = total; i; i--) {
int x = b[i];
t[x].sum = t[x].cnt;
for (map<char, int>::iterator it = t[x].ch.begin(); it != t[x].ch.end(); it++)
t[x].sum += t[it->second].sum;
}
}
string get_kth(int k) {
int x = 1; string ret = "";
if (k > t[x].sum) return "-1";
while (k)
for (map<char, int>::iterator it = t[x].ch.begin(); it != t[x].ch.end(); it++) {
int y = it->second;
if (k <= t[y].sum) {
k -= t[y].cnt;
x = y, ret += it->first;
break;
} else k -= t[y].sum;
}
return ret;
}
};
signed main() {
ios::sync_with_stdio(false);
int t, k;
string str;
cin >> str >> t >> k;
SAM::init(str);
SAM::topo_sort();
SAM::compute(t);
cout << SAM::get_kth(k) << endl;
return 0;
}