P3975 [TJOI2015]弦论 (SAM)
题目链接
https://www.luogu.com.cn/problem/P3975
题意
给你一个仅由小写字母构成的字符串\(s\),输出它的第\(k\)小子串(\(t=0\)时表示不同位置的相同子串算作一个,\(t=1\)时表示不同位置的相同子串算作多个)
思路
构建出SAM之后,求出\(sum[i]\),表示有\(sum[i]\)个子串经过\(i\)号点。
\(siz[i]\)表示\(i\)所代表的\(endpos\)的集合大小,也就是\(i\)所对应字符串集合的出现次数。
\(t=0\)时,本质相同的子串在不同位置出现算相同,所以\(siz[i]=1\),即将每个字符串集合的\(endpos\)集合大小(字符串集合元素出现次数)置为\(1\)
\(t=1\)时,本质相同的子串在不同位置出现算不同,那么累加后的\(siz\)表示实际上\(endpos\)的集合大小
在SAM的树结构上\(dp\)求\(siz\)
在SAM的图结构上\(dp\)求\(sum\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxx = 2*1e6+10;
char s[maxx];
int last=1,tot=1,fa[maxx],ch[maxx][26],len[maxx];
LL siz[maxx],sum[maxx];
void add(int x)
{
int pre=last,now=last=++tot;
len[now]=len[pre]+1;siz[now]=1;
for(;pre&&!ch[pre][x];pre=fa[pre])ch[pre][x]=now;
if(!pre)fa[now]=1;
else
{
int q=ch[pre][x];
if(len[q]==len[pre]+1)fa[now]=q;
else
{
int nows=++tot;
len[nows]=len[pre]+1;
memcpy(ch[nows],ch[q],sizeof(ch[q]));
fa[nows]=fa[q];fa[q]=fa[now]=nows;
for(;pre&&ch[pre][x]==q;pre=fa[pre])ch[pre][x]=nows;
}
}
}
int head[maxx],to[maxx],ne[maxx],cnt;
int vis[maxx];
void addm(int u,int v)
{
to[++cnt]=v,ne[cnt]=head[u],head[u]=cnt;
}
void dfs1(int u)
{
for(int i=head[u];i;i=ne[i])
{
dfs1(to[i]);
siz[u]+=siz[to[i]];
}
}
void dfs2(int u)
{
if(vis[u])return;
vis[u]=1;
for(int i=0;i<26;i++)
{
int v=ch[u][i];
if(v)dfs2(v),sum[u]+=sum[v];
}
}
void solve(int u,int k)
{
if(k<=siz[u])return;
k-=siz[u];
for(int i=0;i<26;i++)
{
int v=ch[u][i];
if(!v)continue;
if(k>sum[v])k-=sum[v];
else
{
putchar(i+'a');
solve(v,k);
return;
}
}
}
int main()
{
scanf("%s",s+1);
int t,k;
scanf("%d%d",&t,&k);
int n=strlen(s+1);
for(int i=1;i<=n;i++)add(s[i]-'a');
for(int i=2;i<=tot;i++)addm(fa[i],i);
dfs1(1);
for(int i=1;i<=tot;i++)
{
if(t)sum[i]=siz[i];
else sum[i]=siz[i]=1;
}
sum[1]=siz[1]=0; //根的点权为0
dfs2(1);
if(sum[1]<k)puts("-1");
else solve(1,k),puts("");
return 0;
}