hdu 5008 (后缀数组 + rmq +二分)

题意:给出一个字符串,求出第k小的子串,并求出字符串的起止位置,如果有多个重复的子串,求出位置最靠左的子串。
思路:比赛时,想到了要用后缀数组,但是没想到如何做。
其实,因为子串是后缀的前缀,后缀数组对后缀排序的同时,也对子串进行了排序。对于每一个sa[i],会产生不同的n - sa[i] - height[i]个子串,这些子串也是排好序的。
这样,我们就我们就可以二分求出这些子串中的第k小的子串。
但是,因为我们没有考虑LCP,这样会导致对于相同的第k小,有些位置更靠左,且存在于公共前缀中的子串没有考虑到,这样,我们还需要对这些字符串进行考虑。
那如何求出最靠左的子串呢?首先要注意,前面找到的子串是在sa数组中,最先出现的子串,但不是在整个字符串最先出现的子串。我们从找到该子串的sa的位置开始,向后搜索,如果和该位置的LCP不小于找到子串的长度,那说明是可能的位置。因为,对于给定位置的LCP,他的长度是不增的,我们可以二分去找到整个可能的范围。最后在这个区间求一次RMQ就是最左的位置。

注意的点:第一个sa[0] 是最后添加的0字符,因为那个最大,第二个点 无论是sa 还是 height 从1开始,因为0都是最后我们添加的0,所以不会有影响。还有就是一些小细节 sa的值是从0开始的。

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <cmath>  
#include <algorithm>  
#include <vector>  
using namespace std;  
#define maxn 240080  
#define inf 0x3f3f3f3f  
#define LL long long int  
int str[maxn],vis[maxn];  
char s[maxn];  
int sa[maxn],t[maxn],t2[maxn],c[maxn],key[maxn];  
int height[maxn],Rank[maxn];  
int len1,len2;  
int Min[maxn][20],lca[maxn][20];  
LL dp[maxn];  
inline int max(int a,int b)  
{  
    return a>b?a:b;  
}  
/* 
用SA模板注意在最后添加一个比所有字符都小的字符。 
key[n] = 0; 
build_sa(key,n+1,m); 
getHeight(key,n+1); 
显然sa[0] 就是最后那个位置。。。 
height[i] 表示 sa[i] 和 sa[i-1] 的最长公共前缀。。 
*/  

void build_sa(int * s,int n,int m)  
{  
    int i,*x = t,*y = t2;  
    for(i = 0;i < m;i++)    c[i] = 0;  
    for(i = 0;i < n;i++)    c[ x[i] = s[i] ]++;  
    for(i = 1;i < m;i++)    c[i] += c[i-1];  
    for(i = n-1;i >= 0;i--)    sa[--c[x[i]]] = i;  
    for(int k = 1;k <= n;k <<= 1)  
    {  
        int p = 0;  
        for(i = n - k;i < n;i++)    y[p++] = i;  
        for(i = 0;i < n;i++)    if(sa[i] >= k)    y[p++] = sa[i] - k;  
        for(i = 0;i < m;i++)    c[i] = 0;  
        for(i = 0;i < n;i++)    c[ x[y[i]] ]++;  
        for(i = 0;i < m;i++)    c[i] += c[i-1];  
        for(i = n-1;i >= 0;i--)    sa[--c[x[y[i]]]] = y[i];  
        //根据sa和y数组计算新的数y组  
        swap(x,y);  
        p = 1;    x[sa[0]] = 0;  
        for(i = 1;i < n;i++)  
            x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p-1:p++;  
        if(p >= n)    break;  
        m = p;  
    }  
}  

void getHeight(int * s,int n)  
{  
    int i,j,k = 0;  
    for(i = 0;i < n;i++)    Rank[sa[i]] = i;  
    for(i = 0;i < n;i++)      
    {  
        if(k) k--;  
        int j = sa[Rank[i]-1];  
        while(s[i+k] == s[j+k])        k++;  
        height[Rank[i]] = k;  
    }  
}  

void RMQ_INIT(int n)//求lca  
{  
    for(int i = 1;i < n;i++)    lca[i][0] = height[i];  
    for(int j = 1;(1<<j)<=n;j++)  
    {  
        for(int i = 0;i+(1<<j)-1<n;i++)  
        {  
            lca[i][j] = min(lca[i][j-1],lca[i+(1<<(j-1))][j-1]);  
        }  
    }  
}  

int RMQ_Query(int l,int r)  
{  
    int k = 0;  
    while((1<<(k+1) <= r-l+1)) k++;  
    return min(lca[l][k],lca[r-(1<<k)+1][k]);  
}  
void RMQ_INIT1(int n)//求区间最小,这是求出sa后求得  
{  
    for(int i = 1;i < n;i++)    key[i] = sa[i]+1;  
    for(int i = 1;i < n;i++)    Min[i][0] = key[i];  
    for(int j = 1;(1<<j)<=n;j++)  
    {  
        for(int i = 0;i+(1<<j)-1<n;i++)  
        {  
            Min[i][j] = min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);  
        }  
    }  
}  
int RMQ_Query1(int l,int r)//  
{  
    int k = 0;  
    while((1<<(k+1)) <= r-l+1) k++;  
    return min(Min[l][k],Min[r-(1<<k)+1][k]);  
}  
int main()  
{  
   // freopen("in.txt","r",stdin);  
    while(scanf("%s",s)!=EOF)  
    {  
        int len = strlen(s);  
        for(int i = 0;i < len;i++)  
            str[i] = s[i]-'a'+2;  
        str[len] = 0;  
        build_sa(str,len+1,30);  
        getHeight(str,len+1);  
        dp[0] = 0;  
        dp[1] = len-sa[1];  
        for(int i = 2;i <= len;i++)  
        {  
            LL add = len-sa[i]-height[i];  
            dp[i] = dp[i-1] + add;  
        }  
        RMQ_INIT(len+1);  
        RMQ_INIT1(len+1);  
        LL l = 0,r = 0,v;  
        int q;    scanf("%d",&q);  
        while(q--)  
        {  
            scanf("%I64d",&v);  
            LL k = (l^r^v)+1;  
            if(k > dp[len])  
            {  
                l = r = 0;  
                cout << 0 << " " << 0 << endl;  
                continue;  
            }  
            int pos = lower_bound(dp+1,dp+1+len,k)-dp;  
            k -= dp[pos-1];  
            int L = sa[pos];  
            int R = L+k+height[pos]-1;////非常没问题  
            //这样从L 到 R就是所要求的字符串,但是还不是满足最小的序号的  
            int Len = R-L+1;  
            int ll = pos+1,rr = len;  
            int ans = pos;  
            while(ll <= rr)  
            {  
                int mid = (ll+rr)>>1;  
                if(RMQ_Query(pos+1,mid) < Len) rr = mid-1;  
                else   
                {  
                    ans = mid;  
                    ll = mid+1;  
                }  
            }  
            int fuck = RMQ_Query1(pos,ans);  
            l = fuck,r = fuck+Len-1;  
            printf("%I64d %I64d\n",l,r);  
        }  
    }  
    return 0;  
}  
posted @ 2017-09-19 21:18  黑码的博客  阅读(98)  评论(0编辑  收藏  举报