2014-team1/kth_substring

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

{{{
// 求字符串str中第k大的互不相同的子串, 及其第一次出现的位置.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair <int, int> PII;
const int MN=2e5+7;

struct ST{
    int mx[MN][20], log2[MN];
    int n;
    void init(int n, int a[]){
        log2[0]=-1; this->n=n;
        for (int i=1; i<=n; ++i) log2[i]=log2[i>>1]+1;
        for (int i=0; i<n; ++i) mx[i][0]=a[i];
        for (int j=1; (1<<j)<n; ++j){
            for (int i=0; i+(1<<j)<=n; ++i){
                mx[i][j]=min(mx[i][j-1], mx[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int rmq(int a, int b){
        if (a>b) swap(a,b);
        int k=log2[b-a+1];
        return min(mx[a][k], mx[b-(1<<k)+1][k]);
    }
} AC,HI;
// AC用来求sa的rmq
// HI用来求height的rmq

//-----------------------后缀数组-----------------------

int T[MN],sa[MN],tsa[MN],rank[MN*2],trank[MN],c[MN],height[MN];

void bsort(int k,int n){
    int i;
    memset(c,0,sizeof(c));
    for (i=0; i<n; i++) c[rank[i+k]]++;
    for (i=1; i<n; i++) c[i]+=c[i-1];
    for (i=n-1; i>=0; i--) tsa[--c[rank[i+k]]]=i;
    memset(c,0,sizeof(c));
    for (i=0; i<n; i++) c[rank[i]]++;
    for (i=1; i<n; i++) c[i]+=c[i-1];
    for (i=n-1; i>=0; i--) sa[--c[rank[tsa[i]]]]=tsa[i];
}

void getsa(int *T, int *sa, int n){
    int i,k,p;
    memset(rank,0,sizeof(rank));
    memset(c,0,sizeof(c));
    for (i=0; i<n; i++) c[T[i]]++;
    for (i=1; i<max(n,128); i++) c[i]+=c[i-1];
    for (i=n-1; i>=0; i--) sa[--c[T[i]]]=i;
    rank[sa[0]]=p=1;
    for (i=1; i<n; i++) rank[sa[i]]=(T[sa[i]]==T[sa[i-1]])?p:++p;
    for (k=1; k<=n && p<n; k<<=1){
        bsort(k,n);
        trank[sa[0]]=p=1;
        for (i=1; i<n; i++)
            trank[sa[i]]=(rank[sa[i-1]]==rank[sa[i]] && rank[sa[i-1]+k]==rank[sa[i]+k])?p:++p;
        for (i=0; i<n; i++) rank[i]=trank[i];
    }
}

void getheight(int *T, int *sa, int *height, int n){
    int i,j,k;
    for (i=0; i<n; i++) rank[sa[i]]=i;
    k=0; height[0]=0;
    for (i=0; i<n; i++){
        if (k) k--;
        if (rank[i]==0) continue;
        int j=sa[rank[i]-1];
        while (i+k<n && j+k<n && T[i+k]==T[j+k]) k++;
        height[rank[i]]=k;
    }
}

//------------------------------------------------------

LL pre[MN];

void prefix(int n){
    memset(pre,0,sizeof(pre));
    for (int i=1; i<=n; i++) // Notice!
        pre[i]=pre[i-1]+n-sa[i-1]-height[i-1];
}   // 得到前缀和数组pre, 表示字典序小于等于"以sa[i]开头的后缀"的不同子串的个数.

inline int calc(int be,int len,int n){
    int l=be,r=n-1;
    while (l<r){
        int mid=(l+r+1)>>1;
        int tmp=HI.rmq(be,mid);
        if (tmp<len) r=mid-1; else l=mid;
    }
    return l;
}   // 二分当前子串可能出现的位置的范围, be为起始位置, len为子串长度, n为总的字符串长度

PII getk(LL k, int n){
    int tmp=lower_bound(pre,pre+n,k)-pre-1;
    int len=k-pre[tmp]+height[tmp];
    int range=calc(tmp+1,len,n);
    if (height[range]<len) range--;
    int l=AC.rmq(tmp,range);
    int r=l+len-1;
    // 目标串是区间[tmp, range](sa字典序)内每个后缀的前缀
    return PII(l,r);
}   // 得到字典序第k大的子串在原串中第一次出现的位置, n为字符串长度

void startup(char str[],int n){
    for (int i=0; i<n; i++) T[i]=str[i]-'a';
    getsa(T,sa,n);
    getheight(T,sa,height,n);
    AC.init(n,sa);
    HI.init(n,height);
    prefix(n);
}   // 需先调用该预处理函数, 其中n为字符串长度

/*
使用说明:
    len=strlen(str);
    startup(str, len);
    PII rst=getk(k, len);
则字典序第k大的子串在字符串中最早出现的位置为[rst.first,rst.second]
*/

}}}
// 求字符串str中第k大的互不相同的子串, 及其第一次出现的位置.
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair <int, int> PII;
const int MN=2e5+7;
struct ST{
    int mx[MN][20], log2[MN];
    int n;
    void init(int n, int a[]){
        log2[0]=-1; this->n=n;
        for (int i=1; i<=n; ++i) log2[i]=log2[i>>1]+1;
        for (int i=0; i<n; ++i) mx[i][0]=a[i];
        for (int j=1; (1<<j)<n; ++j){
            for (int i=0; i+(1<<j)<=n; ++i){
                mx[i][j]=min(mx[i][j-1], mx[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int rmq(int a, int b){
        if (a>b) swap(a,b);
        int k=log2[b-a+1];
        return min(mx[a][k], mx[b-(1<<k)+1][k]);
    }
} AC,HI;
// AC用来求sa的rmq
// HI用来求height的rmq
//-----------------------后缀数组-----------------------
int T[MN],sa[MN],tsa[MN],rank[MN*2],trank[MN],c[MN],height[MN];
void bsort(int k,int n){
    int i;
    memset(c,0,sizeof(c));
    for (i=0; i<n; i++) c[rank[i+k]]++;
    for (i=1; i<n; i++) c[i]+=c[i-1];
    for (i=n-1; i>=0; i--) tsa[--c[rank[i+k]]]=i;
    memset(c,0,sizeof(c));
    for (i=0; i<n; i++) c[rank[i]]++;
    for (i=1; i<n; i++) c[i]+=c[i-1];
    for (i=n-1; i>=0; i--) sa[--c[rank[tsa[i]]]]=tsa[i];
}
void getsa(int *T, int *sa, int n){
    int i,k,p;
    memset(rank,0,sizeof(rank));
    memset(c,0,sizeof(c));
    for (i=0; i<n; i++) c[T[i]]++;
    for (i=1; i<max(n,128); i++) c[i]+=c[i-1];
    for (i=n-1; i>=0; i--) sa[--c[T[i]]]=i;
    rank[sa[0]]=p=1;
    for (i=1; i<n; i++) rank[sa[i]]=(T[sa[i]]==T[sa[i-1]])?p:++p;
    for (k=1; k<=n && p<n; k<<=1){
        bsort(k,n);
        trank[sa[0]]=p=1;
        for (i=1; i<n; i++)
            trank[sa[i]]=(rank[sa[i-1]]==rank[sa[i]] && rank[sa[i-1]+k]==rank[sa[i]+k])?p:++p;
        for (i=0; i<n; i++) rank[i]=trank[i];
    }
}
void getheight(int *T, int *sa, int *height, int n){
    int i,j,k;
    for (i=0; i<n; i++) rank[sa[i]]=i;
    k=0; height[0]=0;
    for (i=0; i<n; i++){
        if (k) k--;
        if (rank[i]==0) continue;
        int j=sa[rank[i]-1];
        while (i+k<n && j+k<n && T[i+k]==T[j+k]) k++;
        height[rank[i]]=k;
    }
}
//------------------------------------------------------
LL pre[MN];
void prefix(int n){
    memset(pre,0,sizeof(pre));
    for (int i=1; i<=n; i++) // Notice!
        pre[i]=pre[i-1]+n-sa[i-1]-height[i-1];
}   // 得到前缀和数组pre, 表示字典序小于等于"以sa[i]开头的后缀"的不同子串的个数.
inline int calc(int be,int len,int n){
    int l=be,r=n-1;
    while (l<r){
        int mid=(l+r+1)>>1;
        int tmp=HI.rmq(be,mid);
        if (tmp<len) r=mid-1; else l=mid;
    }
    return l;
}   // 二分当前子串可能出现的位置的范围, be为起始位置, len为子串长度, n为总的字符串长度
PII getk(LL k, int n){
    int tmp=lower_bound(pre,pre+n,k)-pre-1;
    int len=k-pre[tmp]+height[tmp];
    int range=calc(tmp+1,len,n);
    if (height[range]<len) range--;
    int l=AC.rmq(tmp,range);
    int r=l+len-1;
    // 目标串是区间[tmp, range](sa字典序)内每个后缀的前缀
    return PII(l,r);
}   // 得到字典序第k大的子串在原串中第一次出现的位置, n为字符串长度
void startup(char str[],int n){
    for (int i=0; i<n; i++) T[i]=str[i]-'a';
    getsa(T,sa,n);
    getheight(T,sa,height,n);
    AC.init(n,sa);
    HI.init(n,height);
    prefix(n);
}   // 需先调用该预处理函数, 其中n为字符串长度
/*
使用说明:
    len=strlen(str);
    startup(str, len);
    PII rst=getk(k, len);
则字典序第k大的子串在字符串中最早出现的位置为[rst.first,rst.second]
*/