解法一:后缀数组

听说后缀数组解第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. testng+selnium+eclipse的测试框架运用

    一:TestNG在Eclipse中的安装(1)点击eclipse中的Help->Install New Software (2)点击[Add]按钮,输入相应的地址(3)勾选加载出来的TestNG ...

  2. 51nod-字符串连接

    输入n个字符串s[i],你要把他们按某个顺序连接起来,使得字典序最小. (1 <= n <= 100) (每个字符串长度 <= 100) (字符串只包含小写字母) Input 第一行 ...

  3. 线段树合并(【POI2011】ROT-Tree Rotations)

    线段树合并([POI2011]ROT-Tree Rotations) 题意 现在有一棵二叉树,所有非叶子节点都有两个孩子.在每个叶子节点上有一个权值(有nn个叶子节点,满足这些权值为1-n1-n的一个 ...

  4. 打包成ipa包

    http://zengwu3915.blog.163.com/blog/static/27834897201362831449893/?suggestedreading&wumii Xcode ...

  5. Numpy的使用规则

    之前安装的python版本是3.7 各种库都是自己一个一个下载安装的 很操心 各种缺功能 后来发现了anaconda 啊 真是一个好东西 简单来说 它就是一个涵盖大部分常用库的python包 一次安装 ...

  6. 【转】工具系列:IntelliJ IDEA (Mac) 运行速度优化

    转自工具系列:IntelliJ IDEA (Mac) 运行速度优化 感谢该作者解决了我使用idea debug很慢的问题 背景 IDEA 下运行程序,经常假死 5 s,作为 Mac 怎么能允许暂停 5 ...

  7. EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER

    EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER ...

  8. 使用wpa_supplicant连接WIFI

    让树莓派可以开机就连接制定的wifi, 可以通过wpa_supplicant来实现. 在 /etc/wpa_supplicant 下写一个配置文件: wpa_supplicant.conf 内容如下: ...

  9. hexo博客谷歌百度收录踩坑日记

    title: hexo博客谷歌百度收录踩坑日记 toc: false date: 2018-04-17 00:09:38 百度收录文件验证 无论怎么把渲染关掉或者render_skip都说我的格式错误 ...

  10. FPGA初学之前后仿真分析

    最近在学习FPGA,感觉语言的学习到时很容易,但是由于缺乏电路图的硬件知识,所以看起来比较难懂,下面是对FPGA中仿真的一点理解,以后需要学习的地方还有很多啊. 一.使用ISE环境进行FPGA系统设计 ...