LibreOJ 2102.「TJOI2015」弦论

学习 SAM ing……

这题有两种情况,其实是类似的。

这个问题相当于,求 SAM 上的字典序 kk 小路径。
类比其他数据结构求解第 kk 大,我们可以对每个结点记录一个 sumsum 代表往这个结点走可以得到的路径数量。
然后对于每个结点,依次尝试走 ,直到不够走才往下。
往下时要减去新结点单独的贡献。
直到 kk 被减成负数为止。

然后来讨论两种情况。

  1. 忽略相同子串的重复贡献。
    直接把每个结点的贡献当做 11 来做(除了根节点)。
    然后 DAG 上 DP 计算 sumsum

  2. 计算相同子串的重复贡献。
    考虑怎么算出每个结点实际的贡献。
    可以在 Parent Tree 上 DP 求 endpos|endpos|,这个其实是个套路。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 5e5;
int n,t,k,a[(N << 1) + 5],c[(N << 1) + 5];
char s[N + 5];
struct node
{
int ch[26];
int fa,len;
} sam[(N << 1) + 5];
int sz[(N << 1) + 5],sum[(N << 1) + 5];
int tot = 1,whole = 1;
void push(int x)
{
int cur = whole,p = whole = ++tot;
sam[p].len = sam[cur].len + 1,sz[p] = 1;
for(;cur && !sam[cur].ch[x];cur = sam[cur].fa)
sam[cur].ch[x] = p;
if(!cur)
sam[p].fa = 1;
else
{
int q = sam[cur].ch[x];
if(sam[cur].len + 1 == sam[q].len)
sam[p].fa = q;
else
{
int nxt = ++tot;
sam[nxt] = sam[q],sam[nxt].len = sam[cur].len + 1,sam[p].fa = sam[q].fa = nxt;
for(;cur && sam[cur].ch[x] == q;cur = sam[cur].fa)
sam[cur].ch[x] = nxt;
}
}
}
int main()
{
scanf("%s%d%d",s + 1,&t,&k),n = strlen(s + 1);
for(register int i = 1;i <= n;++i)
push(s[i] - 'a');
for(register int i = 1;i <= tot;++i)
++c[sam[i].len];
for(register int i = 1;i <= tot;++i)
c[i] += c[i - 1];
for(register int i = tot;i;--i)
a[c[sam[i].len]--] = i;
for(register int i = tot;i;--i)
t ? sz[sam[a[i]].fa] += sz[a[i]] : sz[a[i]] = 1;
sz[1] = 0;
for(register int i = tot;i;--i)
{
sum[a[i]] = sz[a[i]];
for(register int j = 0;j < 26;++j)
if(sam[a[i]].ch[j])
sum[a[i]] += sum[sam[a[i]].ch[j]];
}
if(k > sum[1])
puts("-1");
else
{
int p = 1;
while(k > 0)
{
register int i = 0;
for(;k > sum[sam[p].ch[i]];++i)
k -= sum[sam[p].ch[i]];
k -= sz[p = sam[p].ch[i]],putchar('a' + i);
}
}
}