解法一:后缀数组

听说后缀数组解第k小本质不同的子串是一个经典问题。

把后缀排好序后第i个串的本质不同的串的贡献就是\(n-sa[i]+1-LCP(i,i-1)\)然后我们累加这个贡献,看到哪一个串的时候,这个贡献的和大于等于k,然后答案就在这个串里了,然后枚举就行了。

那么第k小子串该怎么办?

我们考虑二分答案,我们按字典序大小二分一个子串(具体就是二分第k小的本质不同子串,因为这个串可以\(O(n)\)求),然后看看比这个串小的串有多少个?然后改变上下界就行了。

那么我们如何求出比一个串小的串有多少个?

设我们我们二分的子串是后缀数组排名为x的后缀的前缀,长度为len。贡献就是\(\sum_{i=1}^{x-1}n-sa[i]+1+\sum_{i=x}^{n}min(LCP(x,i),len)\)

然后这个题就解决了。

代码很丑

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=501000;
int c[N],x[N],sa[N],y[N],height[N],rk[N],n,m,t,k,tmp,ans;
char s[N];
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
void get_sa(){
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int num=0;
for(int i=n-k+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
for(int i=1;i<=n;i++)swap(x[i],y[i]);
x[sa[1]]=1;num=1;
for(int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
if(n==num)return;
m=num;
}
}
void get_height(){
int k=0;
for(int i=1;i<=n;i++)rk[sa[i]]=i;
for(int i=1;i<=n;i++){
if(rk[i]==1)continue;
if(k)k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
height[rk[i]]=k;
}
}
int judge(int x){
int num=0;
tmp=0;
for(int i=1;i<=n;i++){
if(tmp+n-sa[i]+1-height[i]>=x){
int len=0;
for(int j=sa[i];j-sa[i]+1-height[i]<=x-tmp;j++)len++;
int mn=height[i+1];
num+=len;
for(int j=i+1;j<=n;j++){
mn=min(height[j],mn);
if(height[j]<len){
for(int k=j;k<=n;k++){
mn=min(height[k],mn);
num+=mn;
}
return num;
}
num+=len;
}
}
num+=n-sa[i]+1;
tmp=tmp+n-sa[i]+1-height[i];
}
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
m=122;
get_sa();get_height();
t=read();k=read();
if(n*(n+1)/2<k){
printf("-1");
return 0;
}
if(t==0){
for(int i=1;i<=n;i++){
if(tmp+n-sa[i]+1-height[i]>=k){
for(int j=sa[i];j-sa[i]+1-height[i]<=k-tmp;j++)printf("%c",s[j]);
return 0;
}
tmp=tmp+n-sa[i]+1-height[i];
}
}
else{
int l=1,r=k;
while(l<=r){
int mid=(l+r)>>1;
if(judge(mid)>=k){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
tmp=0;
for(int i=1;i<=n;i++){
if(tmp+n-sa[i]+1-height[i]>=ans){
for(int j=sa[i];j-sa[i]+1-height[i]<=ans-tmp;j++)printf("%c",s[j]);
return 0;
}
tmp=tmp+n-sa[i]+1-height[i];
}
}
return 0;
}

解法二 后缀自动机

表示后缀自动机根本不会用。555

trans数组看做边的话一个\(DAG\),从这个\(root\)出发的每一条路径对应原串的一个子串这些子串都是本质不同的。我们可以做一个DP求出从一个点出发的所有路径有多少条路径转移方程\(dp[u]=1+\sum dp[v]\)。然后再在图上像类似线段树上二分的方法就可以求出答案了。

那么第二问该怎么办?

我们注意到一个串出现的次数就是后缀树中这个节点的子树内的后缀节点数(就是代表一个串结束的节点数)。所以我们可以仿照第一问的方案,只不过DP的方程改为了\(dp[u]=size[u]+\sum dp[v]\)(这里的\(size[u]\)代表后缀树中\(u\)的子树的后缀节点数)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1001000;
int tot=1,u=1,len[N],size[N],fa[N],trans[N][27],n,t,k,f[N],c[N],A[N];;
bool vis[N];
char s[N];
void ins(int c){
int x=++tot;size[x]=1;
len[x]=len[u]+1;
for(;u&&trans[u][c]==0;u=fa[u])trans[u][c]=x;
if(u==0)fa[x]=1;
else{
int v=trans[u][c];
if(len[u]+1==len[v])fa[x]=v;
else{
int w=++tot;
len[w]=len[u]+1;
memcpy(trans[w],trans[v],sizeof(trans[w]));fa[w]=fa[v];
fa[v]=fa[x]=w;
for(;u&&trans[u][c]==v;u=fa[u])trans[u][c]=w;
}
}
u=x;
}
void work(int x,int k){
if(k<=size[x]) return;
k-=size[x];
for(int i=1;i<=26;i++){
int R=trans[x][i]; if(!R) continue;
if(k>f[R]) {k-=f[R];continue;}
putchar(i+'a'-1);work(R,k);return;
}
}
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)ins(s[i]-'a'+1);
t=read();k=read();
if(n*(n+1)/2<k){printf("-1");return 0;}
for(int i=1;i<=tot;i++)c[len[i]]++;
for(int i=1;i<=tot;i++)c[i]+=c[i-1];
for(int i=1;i<=tot;i++)A[c[len[i]]--]=i;
for(int i=tot;i>=1;i--)size[fa[A[i]]]+=size[A[i]];
for(int i=1;i<=tot;i++)t==0?(f[i]=size[i]=1):(f[i]=size[i]);
size[1]=f[1]=0;
for(int i=tot;i>=1;i--)
for(int j=1;j<=26;j++)
if(trans[A[i]][j])f[A[i]]+=f[trans[A[i]][j]];
work(1,k);
return 0;
}

解法三:后缀树

也是类似线段树二分的思想跟SAM差不多,不过不是在图里二分了,在树上二分。

最新文章

  1. Building GCC 4.1.2 in CentOS 7 for Maya API development
  2. CAST 类型转换应用
  3. OI 中的 FFT
  4. promise理解
  5. Java 集合深入理解(4):List&lt;E&gt; 接口
  6. 【bzoj1085】[SCOI2005]骑士精神
  7. 关于KeyEvent.Callback
  8. 假如我来架构12306网站---文章来自csdn(Jackxin Xu IT技术专栏)
  9. SQL运维
  10. docker 数据卷和docker数据卷容器以及数据卷的备份和还原
  11. golang文件操作
  12. [原创]EBAZ4205 Linux log打印输出
  13. MySQL执行原理,逻辑分层、更改数据库处理引擎
  14. Hdoj 1847.Good Luck in CET-4 Everybody! 题解
  15. isolate demo
  16. centos7安装python,mariaDB,django,nginx
  17. java.lang.NoSuchMethodException: .&lt;init&gt;()
  18. Linux下/usr/bin与/usr/local/bin/区别总结
  19. Notes中几个处理多值域的通用函数
  20. 洛谷P1126 机器人搬重物【bfs】

热门文章

  1. 页面定制CSS代码初探(一):页面变宽 文本自动换行 图片按比缩放
  2. ZBrush 4R7中自定义笔刷
  3. day21 模块
  4. Windows10 下安装 Mongodb
  5. web 安全主题
  6. Java基础学习总结(59)——30 个java编程技巧
  7. Floodlight 中创建消息对象的方法
  8. Linux文件查找命令具体解释-which whereis find locate
  9. HDU 5416 CRB and Tree (2015多校第10场)
  10. linux下通过命令启动多个终端运行对应的命令和程序