「BZOJ3998」[TJOI2015] 弦论(第K小子串)
https://www.lydsy.com/JudgeOnline/problem.php?id=3998
Description
对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input
第一行是一个仅由小写英文字母构成的字符串S
第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。
Output
输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1
Sample Input
aabc
0 3
0 3
Sample Output
aab
Sum值代表从当前状态出发不同的路径条数,即将孩子们的路径条数累加起来,再加上本身的s值。即sum[i]=s[i]+∑sum[j](j=next[i][k],k=0..25)
预处理结束之后,通过dfs找出第k小的路径。这有点类似与二十六分,每次先按字典序往后走,如果当前节点的s值大于当前的k,则说明到当前节点为止,退出dfs;否则k先减去当前s的大小。如果当前节点的sum值大于当前的k值,说明终止点再它的孩子中,输出当前节点对应的字母,k并继续往下深dfs;如果当前结点的sum值小于k,说明k大的子串不在这条路径上,直接将k减去sum并继续搜索下一条路径。(说起来有点绕,直接看代码)
#include <bits/stdc++.h>
#define LL long long
#define P pair<int, int>
#define lowbit(x) (x & -x)
#define mem(a, b) memset(a, b, sizeof(a))
#define rep(i, a, n) for (int i = a; i <= n; ++i)
const int maxn = 500001;
#define mid ((l + r) >> 1)
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
// __int128 read() { __int128 x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f;}
// void print(__int128 x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) print(x / 10); putchar(x % 10 + '0');}
const LL mod = 1e9 + 7;
int len,T,k;
struct SAM{
int trans[maxn<<1][26], slink[maxn<<1], maxlen[maxn<<1];
// 用来求endpos
int indegree[maxn<<1], endpos[maxn<<1], rank[maxn<<1], ans[maxn<<1];
// 计算所有子串的和(0-9表示)
LL sum[maxn<<1];
int last, now, root;
inline void newnode (int v) {
maxlen[++now] = v;
mem(trans[now],0);
}
inline void extend(int c) {
newnode(maxlen[last] + 1);
int p = last, np = now;
// 更新trans
while (p && !trans[p][c]) {
trans[p][c] = np;
p = slink[p];
}
if (!p) slink[np] = root;
else {
int q = trans[p][c];
if (maxlen[p] + 1 != maxlen[q]) {
// 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q]
newnode(maxlen[p] + 1);
int nq = now;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
slink[nq] = slink[q];
slink[q] = slink[np] = nq;
while (p && trans[p][c] == q) {
trans[p][c] = nq;
p = slink[p];
}
}else slink[np] = q;
}
last = np;
// 初始状态为可接受状态
endpos[np] = 1;
}
inline void init()
{
root = last = now = 1;
slink[root]=0;
mem(trans[root],0);
}
inline void getEndpos() {
// topsort
for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数
for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; // 统计度数小于等于 i 的节点的总数
for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; // 为每个节点编号,节点度数越大编号越靠后
// 从下往上按照slik更新
for (int i = now; i >= 1; --i) {
int x = rank[i];
// printf("%d ",x);
if(T==1)
endpos[slink[x]] += endpos[x];
else endpos[x]=1;
}
endpos[1]=0;//不要忘了根节点是虚点
for(int i=now ; i>=1 ; i--)
{
int x = rank[i];
sum[x]=endpos[x];
for(int j=0 ; j<26 ; j++)///后面可以接的字符
sum[x]+=sum[trans[x][j]];
}
}
void dfs(int x,int K)
{
if(K<=endpos[x]) return ;
K-=endpos[x];
for(int i=0 ; i<26 ; i++)
{
int p=trans[x][i];
if(p)
{
if(K<=sum[p])
{
printf("%c",i+'a');
dfs(p,K);
return ;
}
K-=sum[p];
}
}
}
}sam;
int main()
{
string str;cin>>str>>T>>k;
sam.init();
len=str.size();
for(int i=0 ; i<len ; i++)
sam.extend(str[i]-'a');
sam.getEndpos();
sam.dfs(sam.root , k);
//- sam.all();
}