某天……咸鱼我问学长
”学后缀自动机看啥啊“
“clj的ppt,慢慢看”
哦……好啊……clj大爷的ppt的确是学后缀自动机的必备材料23333……然而没图啊QuQ,全程没图根本看不懂啦
于是开始翻blog
1.这个blog 配合ppt食用,基本能懂了。这家的图很多……比较方便理解【而且举的例子不是回文串23333
2.一觉醒来忘记SAM是个啥时,这个blog 是不错的选择
3.黄学长的这个SAM,虽然作为模板并不算简洁优美……但是真的比模板容易理解【趴倒
==============================================================================
于是写写自己对后缀自动机的理解吧QuQ
……在学SAM前是先食用了AC自动机和trie树的……于是总觉得SAM的pre指针和AC自动机的fail指针神tm相似,不怂就是怼x
在SAM里能找到这个串的所有子串……子串数量是n^2级别的,如果直接建成trie的话肯定有n^2级别的节点数,但SAM的节点数≤2n……
很明显一个节点能代表的串不止一个……
SAM把一堆后缀相同的子串合并了,于是减少了很多重复的节点
在建SAM的时候,如果发现在新建节点时有冲突,而且长度不同的话,会重新复制一个节点【设原有节点为q,新节点为nq】,q的pre会指向nq,而pre[x]的意思是x节点无法记录的最长后缀所在节点处……也不知道我的理解对不对(如果哪位神犇发现我是错的跪求指教QuQ)……其实这nq和q本质上是一个节点……至少用途上是,所以在计数赋初值的时候……复制出来的那个节点的siz是0
可以理解成……SAM其实就是一棵trie树,但是吧他们相同的后缀合并了,于是很多节点共用了一些儿子,也共用了一些父亲,共用儿子不在意,但是共用父亲的话会出事(一个节点不会有两个标号相同的边往外连) 于是就复制了一个假的父亲,用来进行一些py交易【大雾】,进行后续的更新
==============================================================================
顺便吐槽……为什么……这道题的代码都没有注释啦……大家都说是裸题然而我这种数据结构弱菜表示……看不懂啊看不懂……
而且写这道题的方法大概有两种……
一种是bfs之后再dfs的,传说在T的边缘……于是我这种自带INF常数的弱菜果断放弃
第二种就是计数排序胡搞乱搞【详情见后文注释】的……代码复杂度和时间复杂度都十分的优【可是好难懂啊为什么没有注释w(゚Д゚)w naive的新人表示快要哭出来了】
#include<bits/stdc++.h>
#define MAXN 500057
using namespace std; int n,t,k;
int son[MAXN<<1][26],pre[MAXN<<1],dis[MAXN<<1];
int last=1,cnt=1;
int siz[MAXN<<1];
void insert(int x){
int p=last , np=++cnt;
dis[np]=dis[p]+1;
siz[np]=1;
for(;p&&!son[p][x];p=pre[p]) son[p][x]=np;
last=np;
if(!p) return pre[np]=1,void();
int q=son[p][x];
if(dis[q]==dis[p]+1) pre[np]=q;
else{
int nq=++cnt; dis[nq]=dis[p]+1;
pre[nq]=pre[q];
pre[np]=pre[q]=nq;
memcpy(son[nq],son[q],sizeof son[q]);
for(;p&&son[p][x]==q;p=pre[p]) son[p][x]=nq;
}
}
int v[MAXN<<1],qq[MAXN<<1];
int sum[MAXN<<1];
void getsize(){
for(int i=1;i<=cnt;++i) ++v[dis[i]];
for(int i=1;i<=n;++i) v[i]+=v[i-1];
for(int i=1;i<=cnt;++i) qq[v[dis[i]]--]=i;
//这上面是计数排序,把所有节点按照拓扑序排了一遍【SAM显然是个DAG
for(int i=cnt;i;--i)
if(t) siz[pre[qq[i]]]+=siz[qq[i]];
else siz[qq[i]]=1;
//↑如果不同位置的子串只算一次的话,就不用管right集合大小了(right集合大小是这个子串出现的次数),全部赋值为1;如果不同位置的子串算多次的话,就按照parent树里从叶子节点一层一层往上推的顺序,计算每个串出现的次数
siz[1]=0;
for(int i=cnt;i;--i){
int t=qq[i];
sum[t]=siz[t];
for(int j=0;j<26;++j)
if(son[t][j]) sum[t]+=sum[son[t][j]];
}//这个就是统计每个节点往后推有多少个子串了……显然……
}
char s[MAXN];
int main(){
scanf("%s%d%d",s,&t,&k);
n=strlen(s);
for(int i=0;i<n;++i) insert(s[i]-'a');
getsize();
if(k>sum[1]) return puts("-1"),0;
int ii=1;
while((k-=siz[ii])>0){
int j=0;
while(k>sum[son[ii][j]]) k-=sum[son[ii][j++]];
ii=son[ii][j];
putchar('a'+j);
}
//上面那个……应该yy一下就支持了吧
return 0;
}