P3975 [TJOI2015]弦论(后缀自动机)
对于t=0的情况:
在不同位置出现的子串算同一个子串,这里巨大的坑点是要把后缀自动机上除起点以外的所有点的sz置为1(不管它是虚拟节点还是真实节点),这一点萌新还没理解...
然后把所有节点按照len值从小到大排序,然后做一个倒着的转移,每个节点首先能转移出sz个子串,然后加上它后继节点的贡献,可以求出每个节点往后可以转移出多少种子串。
然后贪心的找。
对于t=1的情况,和以前一样,求出每个节点所代表的字符串集合在母串中出现了几次(即子树大小)。
然后把所有节点按照len值从小到大排序,倒着转移。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+100;
int len[maxn],link[maxn],nxt[maxn][26];
int sz[maxn];
int tot=1,lst=1;
string s;
int n,t,k;
void sam_extend (char c) {
int cur=++tot;
len[cur]=len[lst]+1;
sz[cur]=1;
int p=lst;
while (p&&!nxt[p][c-'a']) {
nxt[p][c-'a']=cur;
p=link[p];
}
if (!p) {
link[cur]=1;
}
else {
int q=nxt[p][c-'a'];
if (len[p]+1==len[q]) {
link[cur]=q;
}
else {
int clone=++tot;
len[clone]=len[p]+1;
for (int i=0;i<26;i++) {
nxt[clone][i]=nxt[q][i];
}
link[clone]=link[q];
while (p&&nxt[p][c-'a']==q) {
nxt[p][c-'a']=clone;
p=link[p];
}
link[q]=link[cur]=clone;
}
}
lst=cur;
sz[cur]=1;
}
vector<int> g[maxn];
void dfs (int u) {
for (int v:g[u]) {
dfs(v);
sz[u]+=sz[v];
}
}
int a[maxn];
int sum[maxn];
int cmp (int x,int y) {
return len[x]<len[y];
}
int main () {
cin>>s>>t>>k;
for (char i:s) sam_extend(i);
for (int i=2;i<=tot;i++) g[link[i]].push_back(i);
if (!t)for (int i=2;i<=tot;i++) sz[i]=1;
if (t) dfs(1);
for (int i=1;i<=tot;i++) a[i]=i;
sort(a+1,a+tot+1,cmp);
if (t) sz[1]=0;
for (int i=tot;i>=1;i--) {
sum[a[i]]=sz[a[i]];
for (int j=0;j<26;j++) {
if (nxt[a[i]][j]) {
sum[a[i]]+=sum[nxt[a[i]][j]];
//sum[i]表示节点i往后可以转移出多少条不同的子串
}
}
}
if (k>sum[1]) return printf("-1\n"),0;
int u=1;
while (k>0) {
int p=0;
while (k>sum[nxt[u][p]]) {
k-=sum[nxt[u][p]];
p++;
}
u=nxt[u][p];
printf("%c",p+'a');
k-=sz[u];
}
}