HDU-5008 Boring String Problem(k大子串,后缀系列)

HDU-5008 Boring String Problem(k大子串,后缀系列)

这个题似乎没有办法用后缀自动机写。。。

\[\ \]

后缀数组

由于后缀数组已经排好序,所以可对于\(sa[i]\)考虑其贡献的个数为\(n-sa[i]+1-lcp[i-1]\)

考虑个数累前缀和\(Sum[i]\),二分就\(Sum[p]\ge k\)能得知要找的串在哪个后缀\(p\)的前缀集里,但是这个后缀可能不止出现的一次

求得的长度\(len=k-Sum[p-1]+lcp[p-1]\)就是排名再加上已经出现的

所有包含这个串的后缀是一段连续的区间\(l,r\)满足\(\forall_{i\in [l,r]}LCP(sa[i],p)\ge len\)

所以在\([l,r]\)中找出最小的下标即可

#include<bits/stdc++.h>
using namespace std;

#define reg register
typedef long long ll;
typedef unsigned long long ull;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
    T s=0;
    int f=0;
    while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return f?-s:s;
}

const int N=2e5+10;

int n;
char s[N];
int cnt[N],tmp[N],rk[N],sa[N],lcp[N];

ll Sum[N];

void PreMake(){
    memset(cnt,0,800);
    rep(i,1,n) cnt[(int)s[i]]++;
    rep(i,1,200) cnt[i]+=cnt[i-1];
    rep(i,1,n) rk[i]=cnt[(int)s[i]],sa[i]=i;
    rep(i,n+1,n*2) rk[i]=0;
    for(reg int k=1;k<=n;k<<=1) {
        rep(i,0,n) cnt[i]=0;
        rep(i,1,n) cnt[rk[i+k]]++;
        rep(i,1,n) cnt[i]+=cnt[i-1];
        drep(i,n,1) tmp[cnt[rk[i+k]]--]=i;
        ;
        rep(i,0,n) cnt[i]=0;
        rep(i,1,n) cnt[rk[i]]++;
        rep(i,1,n) cnt[i]+=cnt[i-1];
        drep(i,n,1) sa[cnt[rk[tmp[i]]]--]=tmp[i];
        ;
        rep(i,1,n) tmp[sa[i]]=tmp[sa[i-1]]+(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+k]!=rk[sa[i-1]+k]);
        rep(i,1,n) rk[i]=tmp[i];
    }
    rep(i,1,n) lcp[i]=0;
    for(reg int i=1,h=0;i<=n;++i) {
        if(h) h--;
        int j=sa[rk[i]-1];
        while(i+h<=n && j+h<=n && s[i+h]==s[j+h]) h++;
        lcp[rk[i]-1]=h;
    }
    rep(i,1,n) Sum[i]=Sum[i-1]+n-sa[i]+1-lcp[i-1];
}

int a[20][N],b[20][N],Log[N];

int main(){
    rep(i,2,N-1) Log[i]=Log[i>>1]+1;
    while(~scanf("%s",s+1)) {
        n=strlen(s+1);
        PreMake();
        rep(i,1,n) a[0][i]=lcp[i],b[0][i]=sa[i];
        rep(i,1,17) {
            int len=(1<<(i-1));
            rep(j,1,n) a[i][j]=min(a[i-1][j],a[i-1][j+len]),b[i][j]=min(b[i-1][j],b[i-1][j+len]);
        }
        int a=0,b=0;
        rep(i,1,rd()) {
            ull x=(rd<ull>()^a^b)+1;
            int p=lower_bound(Sum+1,Sum+n+1,x)-Sum;
            if(p>n) a=b=0;
            else {
                int l=p,r=p,len=x-Sum[p-1]+lcp[p-1];
                drep(j,17,0) {
                    if(l-(1<<j)>0 && ::a[j][l-(1<<j)]>=len) l-=(1<<j);
                    if(r+(1<<j)<=n && ::a[j][r]>=len) r+=(1<<j);
                } // 倍增找到所有满足条件的后缀
                int d=Log[r-l+1],res=min(::b[d][l],::b[d][r-(1<<d)+1]); // ST表查询最小起始下标
                a=res,b=a+len-1;
            }
            printf("%d %d\n",a,b);
        }
    }
}

\[\ \]

\[\ \]

后缀树

建树之后直接按照字典序遍历得到即可

最终起始和后缀数组是一样的

#include<bits/stdc++.h>
using namespace std;

#define reg register
typedef long long ll;
typedef unsigned long long ull;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
    T s=0;
    int f=0;
    while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return f?-s:s;
}

const int N=2e5+10;

int n;
char s[N];

int trans[N][26],link[N],len[N],stcnt,lst,End[N];

struct Edge{
    int to,nxt;
} e[N];
int head[N],ecnt;
void AddEdge(int u,int v) {
    e[++ecnt]=(Edge){v,head[u]};
    head[u]=ecnt;
}

void Init(){
    link[0]=-1,len[0]=0;
    rep(i,0,stcnt) {
        head[i]=0;
        rep(j,0,25) trans[i][j]=0;
    }
    lst=stcnt=ecnt=0;
}

void Extend(int c) {
    int cur=++stcnt,p=lst;
    End[cur]=len[cur]=len[p]+1;
    while(~p && !trans[p][c]) trans[p][c]=cur,p=link[p];
    if(p==-1) link[cur]=0;
    else {
        int q=trans[p][c];
        if(len[q]==len[p]+1) link[cur]=q;
        else {
            int clone=++stcnt;
            memcpy(trans[clone],trans[q],104);
            End[clone]=End[q];
            len[clone]=len[p]+1,link[clone]=link[q];
            while(~p && trans[p][c]==q) trans[p][c]=clone,p=link[p];
            link[cur]=link[q]=clone;
        }
    }
    lst=cur;
}


ll Sum[N];
int Pos[N],cnt,LstL,LstR;

struct Node{
    int x,c;
    bool operator < (const Node __) const {
        return c<__.c;
    }
};

void dfs(int u) {
    if(u) {
        Sum[cnt+1]=Sum[cnt]+len[u]-len[link[u]];
        Pos[++cnt]=u;
    }
    vector <Node> son;
    for(reg int i=head[u];i;i=e[i].nxt) {
        int v=e[i].to;
        son.pb((Node){v,s[n-End[v]+len[link[v]]+1]});
    }
    sort(son.begin(),son.end());// 按照字典序遍历
    rep(i,0,son.size()-1) {
        dfs(son[i].x);
        cmax(End[u],End[son[i].x]); // 处理出最前面的位置
    }
}

int main(){
    while(~scanf("%s",s+1)) {
        n=strlen(s+1);
        Init();
        drep(i,n,1) Extend(s[i]-'a'); // 倒着建SAM就是后缀树了
        rep(i,1,stcnt) AddEdge(link[i],i);
        cnt=LstL=LstR=0;
        dfs(0);
        rep(i,1,rd()) {
            ull x=(rd<ull>()^LstL^LstR)+1;
            int p=lower_bound(Sum+1,Sum+cnt+1,x)-Sum; // 二分找到出现位置
            if(p>cnt) puts("0 0"),LstL=LstR=0;
            else printf("%d %d\n",LstL=(n-End[Pos[p]]+1),LstR=(n-End[Pos[p]]+len[link[Pos[p]]]+(x-Sum[p-1])));
        }
    }
}


posted @ 2020-01-19 14:15  chasedeath  阅读(133)  评论(0编辑  收藏  举报