[TJOI2015]弦论(后缀数组or后缀自动机)

解法一:后缀数组

听说后缀数组解第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
构建后缀树(这里讨论的是套路地反着建后缀树)的辅助数组看做,就是边的话一个\(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差不多。

原文地址:https://www.cnblogs.com/Xu-daxia/p/10203608.html

时间: 2024-08-01 00:36:00

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

poj 2774 最长公共子串--字符串hash或者后缀数组或者后缀自动机

http://poj.org/problem?id=2774 想用后缀数组的看这里:http://blog.csdn.net/u011026968/article/details/22801015 本文主要讲下怎么hash去找 开始的时候写的是O(n^2 logn)算法 果断超时...虽然也用了二分的,, 代码如下: //hash+二分 #include <cstdio> #include <cstring> #include <algorithm> #include

利用后缀数组构造后缀树

由于蒟蒻azui前段时间忙着准备省选,并在省选中闷声滚大粗,博客停更了好久.. 省选过后整个人各种颓,整天玩玩泥巴什么的... 前段时间学后缀数组的时候上网查相关资料,看到说后缀数组和后缀树是可以相互转化的,并且uoj上有大量通过后缀自动机建出后缀树然后dfs遍历获得后缀数组的模板,但是通过后缀数组来建后缀树的资料确实稀缺. 也许大牛们都觉得这xjbYY一下就可以写了,所以网上没找到对应的代码,那么我来补个坑吧.大牛勿喷.. 先谈谈我的理解吧.. 讲道理后缀数组和后缀树应该是完全等价的,但前两者

BZOJ 题目3172: [Tjoi2013]单词(AC自动机||AC自动机+fail树||后缀数组暴力||后缀数组+RMQ+二分等五种姿势水过)

3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 1890  Solved: 877 [Submit][Status][Discuss] Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个单词由小写字母组成,N<=200,单词长度不超过10^6

[后缀数组+dp/AC自动机+dp+线段树] hdu 4117 GRE Words

题意: 给你N个字符串, N(1 <= N <= 2w), 所有串的长度加一起不超过30w.每个串有个值.这个值[-1000, 1000]. 问不打乱字符串顺序,从中取若干个字符串,使得前一个串是后一个串的子串,求满足前面调条件的字符串值得和最大,求这个值. 思路: 其实就是一个很明显的dp. dp[i]代表以第i个字符串结尾的最大权值. 但是就是子串这个问题怎么处理. 由于这题数据比较水可以用后缀数组处理这个问题. 将所有字符串拼接,做sa. 每次在height数组里往上和往下寻找公共前缀等

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

http://poj.org/problem?id=2774 我想看看这里的后缀数组:http://blog.csdn.net/u011026968/article/details/22801015 本文主要讲下怎么hash去找 開始的时候写的是O(n^2 logn)算法 果断超时. ..尽管也用了二分的.. 代码例如以下: //hash+二分 #include <cstdio> #include <cstring> #include <algorithm> #incl

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

转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Long Long Message Time Limit: 4000MS   Memory Limit: 131072K Case Time Limit: 1000MS Description The little cat is majoring in physics in the capital of Byterland. A piece of sad news comes t

bzoj 3172 后缀数组|AC自动机

后缀数组或者AC自动机都可以,模板题. /************************************************************** Problem: 3172 User: BLADEVIL Language: C++ Result: Accepted Time:424 ms Memory:34260 kb ****************************************************************/ //By BLADEVI

后缀数组构造

读了罗穗的论文,终于知道后缀数组怎么构造了,还反复打了五遍,打得很痛苦才最终理解. 终于体会到XY的痛苦了.实在是一篇OI生涯中最难懂的代码orz看来两天.一下是经过稍微加长的稍微易理解的代码(其实差不多好吧). 具体注释看 网址 1 #include<cstdio> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 6 struct suffix_array 7 { 8 const l

[Note]后缀数组

后缀数组 代码 void rsort() { for (int i = 1; i <= m; ++i) tax[i] = 0; for (int i = 1; i <= n; ++i) ++tax[rnk[i]]; for (int i = 1; i <= m; ++i) tax[i] += tax[i-1]; for (int i = n; i >= 1; --i) sa[tax[rnk[tmp[i]]]--] = tmp[i]; } void ssort() { for (in