解法一:后缀数组

听说后缀数组解第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. XML教程!

    什么是XML? XML是指可扩展标记语言(eXtensible Markup Language),它是一种标记语言,很类似HTML.它被设计的宗旨是传输数据,而非显示数据.XML标签没有被预定义,需要 ...

  2. [置顶] Elon Musk (伊隆·马斯克):无限的创想与意志的胜利

    Elon Musk (伊隆·马斯克):无限的创想与意志的胜利 很多人说 Steve Jobs 很伟大,这一点我认同.但是,单纯从创造出的产物而言,Elon Musk 的成就毫无疑问远远超越 Steve ...

  3. sqrt开平方算法的尝试,是的看了卡马克大叔的代码,我来试试用C#写个0x5f3759df和0x5f375a86跟System.Math.Sqrt到底哪个更强

    今天笔试遇到一个代码题,要求写一个开平方算法,回来发现了雷神之锤里的一段神代码: float Q_rsqrt( float number ) { long i; float x2, y; const ...

  4. [luogu] P3089 [USACO13NOV]POGO的牛Pogo-Cow

    P3089 [USACO13NOV]POGO的牛Pogo-Cow 题目描述 In an ill-conceived attempt to enhance the mobility of his pri ...

  5. 【codeforces 508D】The Maths lecture

    [题目链接]:http://codeforces.com/problemset/problem/507/D [题意] 让你找符合这样数字的数的个数: 1.有n个数码 2.某个后缀%k的值为0 3.大于 ...

  6. BZOJ——1602: [Usaco2008 Oct]牧场行走 || 洛谷—— P2912 [USACO08OCT]牧场散步Pasture Walking

    http://www.lydsy.com/JudgeOnline/problem.php?id=1602 || https://www.luogu.org/problem/show?pid=2912 ...

  7. 在Html5中与服务器交互

    转自原文 在Html5中与服务器交互 刚刚涉足职场,上头就要我研究HTML5,内嵌到手机上,这对我来说完全是一个陌生的领域,不过也正好给自己一个机会来学习,最近做到要跟服务器交互这部分,这部分可是卡了 ...

  8. n个骰子,和为x的概率分别是多少

    开始我居然又没有想出来.. 还是看了解法.开始的时候,一直想的是用概率,百分比来求,后来才发现,用次数来求,最后除一下,更近清晰. 方法,可以是递归,每次多一个骰子的时候,次数分别加上个数以及上一次i ...

  9. ZOJ 3556

    终于做出来了,激动.... 这道题隐藏得深啊,但若推导下来,就变简单了. 首先,一个集合的子集的个数为2^n=s.注意了,题目求的是有序集合组,并且每个集合是可以重复使用的,怎么办呢?这就要想到多重集 ...

  10. IntelliJ IDEA 初始化项目时No Java SDK Found

    IntelliJ IDEA 初始化项目时No Java SDK Found 自己在Project SDK后面的New按钮进行JDK的添加: