「TJOI2015」弦论
知识点: SAM
原题面 Loj, Luogu
而莲子则是超统一物理学。最近在做弦论方面的研究,还顺利吗?
———— 《梦违科学世纪》永夜の報い ~ Imperishable Night
题意简述
给定长度为 \(n\) 的字符串 和参数 \(t\)。
\(t=0\),求它的第 \(k\) 小本质不同子串。
\(t=1\),求它的第 \(k\) 小子串。
\(1\le n\le 5\times 10^5,\ t<2,\ k\le 10^9\)
分析题意
分享一下做题经历:
算法一
先考虑 \(T=1\):
建 SAM,dfs 求每个状态的出现次数 \(size\)。
再在 DAWG 上按照字典序跑,跑到一个节点就令 \(k- size_i\),并转移。
当 \(k=0\) 时,当前跑到的字符串即为所求。
\(T=0\) 时,直接赋 \(size_i = 1\),即每个状态仅出现 \(1\) 次。
再按上述过程跑即可。
算法二
然后 T 了。
发现这玩意复杂度上限是 \(O(n+k)\) 的,\(T=0\) 时必定达到上限。
跑到一个节点才令 \(k - size_i\),并转移 太慢啦!
考虑权值线段树维护第 k 小的过程:
若当前跑出串 \(S\),考虑下一步转移,向 \(c\) 转移,相当于遍历所有前缀为 \(S+c\) 的串。
考虑向 \(a\) 转移,若所有前缀为 \(S+a\) 的串的数量 \(<k\),答案串一定不以 \(S+a\) 作为前缀。
遍历它们只会浪费时间,仅需使 \(k-\) 所有前缀为 \(S+a\) 的串的数量,再考虑向 \(b\) 转移。
考虑维护以某字符串作为前缀的,所有子串的数量。
这玩意咋维护啊?? 自己 YY 了一波:
考虑 parent树的性质,对于一个状态,其在 parnet 树上的子孙,均以其为 后缀。
统计某状态为 后缀 的子串的数量,可以直接在 parent 树上统计。现在要求统计作为 前缀 的情况,想到建反串的 parent 树,dfs 即可求出以某状态为 前缀 时的子串数量。
在正串的 DAWG 上跑答案时,在转移到某状态前根据它判断即可。
但这玩意复杂度是假的/fad,还是 T 了。
因为不知道反串,正串 SAM 状态的映射关系。
想查询以某状态为 前缀 时的子串数量,只能在反串的 SAM 中把该状态的反串跑出来,复杂度就爆炸了。
这个 idea 可能还不错?以后说不定会用到。
算法三
再 YY 一波:
考虑在 SAM 上暴跳时的搜索树。
发现有许多重复节点,它们的子树还会被跑多次,考虑记忆化子树的 size。
没有去实现,正确性未知。
算法四
一个子串唯一对应 SAM 中的一条路径,第 \(k\) 小子串即第 \(k\) 小路径。
预处理每个状态的路径条数,查询类似权值线段树。
注意预处理路径条数时按照拓扑序 DP,
用 \(u\) 可转移到的节点更新 \(u\)。
需要先将各状态按照 \(len\) 进行排序,为保证复杂度使用了计数排序。
总复杂度 \(O(|S| + |ans|\cdot |\sum|)\)。
代码实现
算法一
//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 5e5 + 10;
const int kMaxm = 26;
//=============================================================
int n, m, t, k, last = 1, node_num = 1;
int ch[kMaxn << 1][kMaxm], link[kMaxn << 1], len[kMaxn << 1], size[kMaxn << 1];
int edge_num, head[kMaxn << 1], v[kMaxn << 1], ne[kMaxn << 1];
char S[kMaxn], ans[kMaxn];
bool flag;
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void Insert(int c_) {
int p = last, now = last = ++ node_num;
size[now] = 1, len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return ;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return ;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof (ch[q]));
link[newq] = link[q];
len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}
void AddEdge(int u_, int v_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Dfs(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
Dfs(v[i]);
size[u_] += size[v[i]];
}
}
void Query(int u_, int lth) {
if (u_ != 1){
if (size[u_] >= k) {
printf("%s", ans + 1);
flag = true;
return ;
}
k -= size[u_];
}
for (int c = 0; c < kMaxm; ++ c) {
if (flag) return ;
if (ch[u_][c]) {
ans[lth] = c + 'a';
Query(ch[u_][c], lth + 1);
ans[lth] = 0;
}
}
}
//=============================================================
int main() {
scanf("%s", S + 1);
n = strlen(S + 1);
t = read(), k = read();
for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a');
if (t) {
for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
Dfs(1);
} else {
for (int i = 2; i <= node_num; ++ i) size[i] = 1;
}
Query(1, 1);
if (! flag) printf("-1");
return 0;
}
算法二
//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
int n, m, t, k, last = 1, node_num = 1, node_num1;
int ch[kMaxn << 1][kMaxm], link[kMaxn << 1], len[kMaxn << 1];
int edge_num, head[kMaxn << 1], v[kMaxn << 1], ne[kMaxn << 1];
int size[kMaxn << 1], sum[kMaxn << 1];
char S[kMaxn >> 1], ans[kMaxn >> 1];
bool flag;
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void Insert(int c_, int start) {
int p = last, now = last = ++ node_num;
size[now] = 1, len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = start; return ;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return ;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof (ch[q]));
link[newq] = link[q];
len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}
void AddEdge(int u_, int v_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Dfs(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
Dfs(v[i]);
if (! t) {
size[u_] = 1;
} else {
size[u_] += size[v[i]];
}
sum[u_] += sum[v[i]];
}
sum[u_] += size[u_] * (len[u_] - len[link[u_]]);
}
int QuerySum(int u_, int lth_) {
if (! lth_) return u_;
return QuerySum(ch[u_][ans[lth_] - 'a'], lth_ - 1);
}
void Query(int u_, int lth_) {
if (u_ != 1){
int ret = QuerySum(node_num1 + 1, lth_ - 1);
if (sum[ret] < k) {
k -= sum[ret];
return ;
}
if (size[u_] >= k) {
printf("%s", ans + 1);
flag = true;
return ;
}
k -= size[u_];
}
for (int c = 0; c < kMaxm; ++ c) {
if (flag) return ;
if (ch[u_][c]) {
ans[lth_] = c + 'a';
Query(ch[u_][c], lth_ + 1);
ans[lth_] = 0;
}
}
}
//=============================================================
int main() {
scanf("%s", S + 1);
n = strlen(S + 1);
t = read(), k = read();
for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a', 1);
node_num1 = node_num;
last = ++ node_num;
for (int i = n; i >= 1; -- i) Insert(S[i] - 'a', node_num1 + 1);
for (int i = 2; i <= node_num1; ++ i) AddEdge(link[i], i);
for (int i = node_num1 + 2; i <= node_num; ++ i) AddEdge(link[i], i);
Dfs(1); Dfs(node_num1 + 1);
Query(1, 1);
if (! flag) printf("-1");
return 0;
}
算法四
正解
//知识点:SAM
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
int n, m, t, k, last = 1, node_num = 1;
int ch[kMaxn][kMaxm], link[kMaxn], len[kMaxn], size[kMaxn], sum[kMaxn];
int cnt[kMaxn], id[kMaxn];
char S[kMaxn >> 1];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void Insert(int c_) {
int p = last, now = last = ++ node_num;
size[now] = 1, len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return ;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return ;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof (ch[q]));
link[newq] = link[q], len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}
void Prepare() {
for (int i = 1; i <= node_num; ++ i) cnt[len[i]] ++;
for (int i = 1; i <= node_num; ++ i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= node_num; ++ i) id[cnt[len[i]] --] = i;
for (int i = node_num; i; -- i) {
if (t) {
size[link[id[i]]] += size[id[i]];
} else {
size[id[i]] = 1;
}
}
size[1] = 0;
for (int i = node_num; i; -- i) {
sum[id[i]] = size[id[i]];
for (int j = 0; j < kMaxm; ++ j) {
if (ch[id[i]][j]) sum[id[i]] += sum[ch[id[i]][j]];
}
}
}
void Query() {
if (k > sum[1]) {
printf("-1");
return ;
}
for (int now = 1; k > 0; k -= size[now]) {
int p = 0;
for (; k > sum[ch[now][p]]; ++ p) k -= sum[ch[now][p]];
printf("%c", 'a' + p);
now = ch[now][p];
}
}
//=============================================================
int main() {
scanf("%s", S + 1);
n = strlen(S + 1);
t = read(), k = read();
for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a');
Prepare();
Query();
return 0;
}