解法一:后缀数组

听说后缀数组解第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差不多,不过不是在图里二分了,在树上二分。

[TJOI2015]弦论(后缀数组or后缀自动机)的更多相关文章

  1. (持续更新)虚树,KD-Tree,长链剖分,后缀数组,后缀自动机

    真的就是讲课两天,吸收一个月呢! \(1.\)虚树 \(2.\)KD-Tree \(3.\)长链剖分 \(4.\)后缀数组 后缀数组 \(5.\)后缀自动机 后缀自动机

  2. hdu4436-str2int(后缀数组 or 后缀自动机)

    题意:给你一堆字符串,仅包含数字'0'到'9'. 例如 101 123 有一个字符串集合S包含输入的N个字符串,和他们的全部字串. 操作字符串很无聊,你决定把它们转化成数字. 你可以把一个字符串转换成 ...

  3. 字符串数据结构模板/题单(后缀数组,后缀自动机,LCP,后缀平衡树,回文自动机)

    模板 后缀数组 #include<bits/stdc++.h> #define R register int using namespace std; const int N=1e6+9; ...

  4. poj 2774 最长公共子--弦hash或后缀数组或后缀自己主动机

    http://poj.org/problem?id=2774 我想看看这里的后缀数组:http://blog.csdn.net/u011026968/article/details/22801015 ...

  5. poj2774 Long Long Message(后缀数组or后缀自动机)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Long Long Message Time Limit: 4000MS   Me ...

  6. 字符串 --- KMP Eentend-Kmp 自动机 trie图 trie树 后缀树 后缀数组

    涉及到字符串的问题,无外乎这样一些算法和数据结构:自动机 KMP算法 Extend-KMP 后缀树 后缀数组 trie树 trie图及其应用.当然这些都是比较高级的数据结构和算法,而这里面最常用和最熟 ...

  7. bzoj 3172 后缀数组|AC自动机

    后缀数组或者AC自动机都可以,模板题. /************************************************************** Problem: 3172 Us ...

  8. SPOJ694 DISUBSTR --- 后缀数组 / 后缀自动机

    SPOJ694 DISUBSTR 题目描述: Given a string, we need to find the total number of its distinct substrings. ...

  9. POJ3080 POJ3450Corporate Identity(广义后缀自动机||后缀数组||KMP)

    Beside other services, ACM helps companies to clearly state their “corporate identity”, which includ ...

随机推荐

  1. composer install或者update 出错

    composer install或者update  出错Your requirements could not be resolved to an installable set of package ...

  2. CMDB设计

    CMDB(资产管理数据库) CMDB是所有运维工具的数据基础 CMDB包含的内容 用户管理,记录测试,开发,运维人员的用户表 业务线管理,需要记录业务的详情 项目管理,指定此项目用属于哪条业务线,以及 ...

  3. css表格表单和统筹

    css:表格表单和统筹 学习目标 1.表单标签及属性高级 2.表格标签及属性高级 3.CSS统筹 4.BFC概念和应用场景 一.表单标签及属性高级 回顾: 表单的作用:用来收集用户的信息的; 表单的组 ...

  4. HDU 1131

    N个节点的不同的树的数目.这样 随便取一个节点作为根,那么他左边和右边的儿子节点个数就确定了,假定根节点标号为x,那么左子树的标号就从1到x-1,共x-1个,右子树的标号就从x+1到n,共n-x个,那 ...

  5. Oracle性能分析1:开启SQL跟踪和获取trace文件

    当Oracle查询出现效率问题时,我们往往须要了解问题所在,这样才干针对问题给出解决方式.Oracle提供了SQL运行的trace信息,当中包括了SQL语句的文本信息.一些运行统计,处理过程中的等待, ...

  6. C++链接和执行相关错误

    http://blog.csdn.net/pipisorry/article/details/37610401 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文 ...

  7. Boost Log 基本使用方法

    Boost Log 基本使用方法 flyfish 2014-11-5 依据boost提供的代码演示样例,学习Boost Log 的基本使用方法 前提 boost版本号boost_1_56_0 演示样例 ...

  8. 使用Powershell 的获取别的机器WMI类失败解决方法!

    有些时候须要连接多台机器去获取他们的类,可是有些时候我们发现计算机无法连接,这个时候怎么办呢? 请改动组策略中下面配置: 能够使用Gpmc.msc 进行以后.本地计算机策略--计算机配置--管理模板- ...

  9. java中的system.out.println()和JSP中out.println()差别

    out.println()输出到client.     在out.println()中,out是response的实例.是以response为对象进行流输出的,即将内容输出到client.假设在JSP ...

  10. QT中|Qt::Tool类型窗口自动退出消息循环问题解决(setQuitOnLastWindowClosed必须设置为false,最后一个窗口不显示的时候,程序会退出消息循环)

    为application 设置setQuitOnLastWindowClosed属性,确实为true: 将其显示为false; 退出该应该程序不能调用QDialog的close消息槽,只能调用qApp ...