后缀数组入门练习

rank数组建议写成rk,因为rank极有可能会和某个库函数里的东西冲突。。
要注意不同的模板里面排序的起始标号可能也会不同,有些rank和sa都是从1开始的,而有些是从0开始的。

建议在做题前找一个好的模板,做题过程中熟悉模板。

下面只简单说明后缀数组应用与题目。

1.不同子串个数:

每个子串都是一个后缀的前缀,从排名1开始计算,每个子串的贡献是len - sa[i] - height[i]。len-sa[i]是自己这个子串有多少个前缀,减去height就是去掉和排名在它前面的那个串相同的前缀。

2.不重叠的最长相同子串

二分枚举长度k,把height分成几组,只考虑height>=k的那些组,在每组中 l e n = max ( s a [ i ] ) min ( s a [ i ] ) 如果 l e n k 那么就成立。

3.可重叠的最长相同子串

很明显就是 max ( h e i g h t [ i ] )

4.最小循环节

这里是 a n == b ( a b ) 注意必须是等于。
那么就好判断了直接枚举长度k,如果 r m q ( s u f f i x ( 0 ) , s u f f i x ( k ) ) == l e n L 那么就成立,这里复杂度是nlogn,用KMP的话可以直接O(N)实现。

5.最长回文子串

将串翻转接在后面,并在两个串之间插入一个无关字符将他们隔开。遍历查询每个点的左边与右边的LCP,左边的LCP即在对称过去的翻转串中查询(注意长度为奇偶的查询的位置不同)。

6.最长公共子串

hdu 1403 Longest Common Substring
注意子串是连续的,那么直接把两个串前后接在一起,很容易想到答案就是满足一定条件的height的最大值,这个条件就是suffix(i-1),suffix(i)在两个不同串里面,用sa[i-1]和sa[i]判断一下位置就可以了。

7.长度不小于K的公共子串个数

POJ 3415

把两个字符串接起来,然后加一个没出现过的字符隔开,最暴力的求法就是 n 2 的,也就是对每个A或者B,找到rk在它以前的所有串,求他们的贡献LCP。 根据LCP的求法,距离越远的LCP不可能会更大。所以可以用一个单调栈来维护,栈中的height值栈底小栈顶大,具体可以见代码,这里很好的一个地方就是多加了一个cnt,用来统计有效的串,也就是查B的时候A是有效的,查A的时候B是有效的。这样对A和B各自做一次就可以了,因为每个东西最多入栈一次出栈一次,所以复杂度是O(n)的

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;
const int MAXN = 3e5+1000;
typedef long long LL;
char s1[MAXN],s2[MAXN];
int k,n,len1,len2;
pair<int,LL> sta[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],num[MAXN];

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}

void SA(int *r,int n)
{
    int *x=wa,*y=wb,m=0;
    for (int i=0;i<n;i++) m=max(m,r[i]+1);
    for (int i=0;i<m;i++) wd[i]=0;
    for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
    for (int i=1;i<m;i++) wd[i]+=wd[i-1];
    for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
    int p=1;
    for (int j=1;p<n;j<<=1,m=p)
    {
        p=0;
        for (int i=n-j;i<n;i++) y[p++]=i;
        for (int i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
        for (int i=0;i<n;i++) wv[i]=x[y[i]];
        for (int i=0;i<m;i++) wd[i]=0;
        for (int i=0;i<n;i++) ++wd[wv[i]];
        for (int i=1;i<m;i++) wd[i]+=wd[i-1];
        for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
        swap(x,y);x[sa[0]]=0;p=1;
        for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    for (int i=1;i<n;i++) rk[sa[i]]=i;
    int k=0;
    for (int i=0;i<n-1;height[rk[i++]]=k)
    {
        if (k)--k;
        for (int j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
    }
}

void sov()
{
    int cnt=0,top=0;
    LL tot=0,ans=0;
    for (int i=2;i<=n;i++)
    {
        if (height[i]<k) tot=top=0;
        else
        {
            cnt=0;
            if (sa[i-1]<len1) tot += height[i]-k+1,cnt++;
            while (top && sta[top].first>=height[i])
            {
                tot -= sta[top].second*(sta[top].first-height[i]);
                cnt += sta[top].second;
                top -- ;
            }
            sta[++top].first = height[i];
            sta[top].second = cnt;
            if (sa[i] > len1) ans += tot;
        }
    }
    tot=0,top=0;
    for (int i=2;i<=n;i++)
    {
        if (height[i]<k) tot=top=0;
        else
        {
            cnt=0;
            if (sa[i-1]>len1) tot += height[i]-k+1,cnt++;
            while (top && sta[top].first>=height[i])
            {
                tot -= sta[top].second*(sta[top].first-height[i]);
                cnt += sta[top].second;
                top -- ;
            }
            sta[++top].first = height[i];
            sta[top].second = cnt;
            if (sa[i] < len1) ans += tot;
        }
    }
    printf("%lld\n",ans);
}


int main()
{
    while (scanf("%d",&k),k)
    {
        n=0;
        scanf("%s%s",s1,s2);
        len1=strlen(s1);
        for (int i=0;i<len1;i++) num[n++]=s1[i];
        num[n++]=150;
        len2=strlen(s2);
        for (int i=0;i<len2;i++) num[n++]=s2[i];
        num[n] = 0;
        SA(num,n+1);
        sov();
    }

    return 0;
}

8.至少在k个串中出现过的子串

首先我做的题是POJ 3294
这题的要求是在大于一半的串里面出现过,所以要注意是 > n 2
然后就是基本的套路,连起来,二分长度,将height分组,看某个组是否个数大于k。
然后强调几点,因为这题串太多了,通过长度来判断位置就比较麻烦,我是用vis给每个下标做一个标记,标记它属于哪个串。
然后字符串连起来的时候要注意,中间加的另外的字符每次都要不一样,而且不能在给的串中出现,否则会影响答案。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <set>

using namespace std;
const int MAXN = 1100*110;
int num[MAXN],n,k;
char s[110][1010];
int sa[MAXN],rk[MAXN],height[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],tag[MAXN];
bool vis[110];
set<string> ss;
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}

void SA(int *r,int n)
{
    int *x=wa,*y=wb,m=0;
    for (int i=0;i<n;i++) m=max(m,r[i]+1);
    for (int i=0;i<m;i++) wd[i]=0;
    for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
    for (int i=1;i<m;i++) wd[i]+=wd[i-1];
    for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
    int p=1;
    for (int j=1;p<n;j<<=1,m=p)
    {
        p=0;
        for (int i=n-j;i<n;i++) y[p++]=i;
        for (int i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
        for (int i=0;i<n;i++) wv[i]=x[y[i]];
        for (int i=0;i<m;i++) wd[i]=0;
        for (int i=0;i<n;i++) ++wd[wv[i]];
        for (int i=1;i<m;i++) wd[i]+=wd[i-1];
        for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
        swap(x,y);x[sa[0]]=0;p=1;
        for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    for (int i=1;i<n;i++) rk[sa[i]]=i;
    int k=0;
    for (int i=0;i<n-1;height[rk[i++]]=k)
    {
        if (k)--k;
        for (int j=sa[rk[i]-1];r[i+k]==r[j+k];++k);
    }
}

bool judge(int mid)
{
    bool flag=false;
    int cnt=0;
    for (int i=2;i<=n;i++)
    {
        if (height[i] >= mid)
        {
            if (!cnt)
            {
                if (!vis[tag[sa[i-1]]]) cnt++,vis[tag[sa[i-1]]]=true;
                if (!vis[tag[sa[i]]]) cnt++,vis[tag[sa[i]]]=true;
            }
            else
            {
                if (!vis[tag[sa[i]]]) cnt++,vis[tag[sa[i]]]=true;
            }
        }
        else
        {
            if (cnt > k/2)
            {
                if (!flag)
                {
                    ss.clear();
                    flag=true;
                }
                string tmp;
                tmp.clear();
                for (int j=sa[i-1];j<sa[i-1]+mid;j++)
                    tmp += num[j];
                ss.insert(tmp);
            }
            memset(vis,0,sizeof vis);
            cnt=0;
        }
    }
    if (flag) return true;
    else return false;
}


int main()
{
    while (scanf("%d",&k),k)
    {
        memset(tag,0,sizeof tag);
        ss.clear();
        n=0;
        int l=1,r=0;
        for (int i=1;i<=k;i++)
        {
            scanf("%s",s[i]);
            int len=strlen(s[i]);
            r=max(r,len);
            for (int j=0;j<len;j++)
            {
                num[n]=s[i][j];
                tag[n]=i;
                n++;
            }
            num[n++]=300+i;
        }
        num[n-1] = 0;
        SA(num,n);
        int ans=0;
        while (l<=r)
        {
            int mid=(l+r)>>1;
            if (judge(mid))
                l=mid+1;
            else
                r=mid-1;
        }
        if (ss.size()==0)
            printf("?\n");
        else
        {
            for (set<string>::iterator it=ss.begin();it!=ss.end();it++)
            {
                cout<<*it<<endl;
            }
        }
        printf("\n");
    }
    return 0;
}

9.最大重复子串

这题要注意的是,先枚举长度,再查询,第一次查询贡献的循环次数是tmp/L +1。因为tmp%L可能不等于0,所以如果在他们两个前面再连上一截可能可以匹配的更长,所以进行第二次查询,然后要注意判断一下边界。至于让字典序最小,我们可以把所有满足条件的长度都放进一个数组,最后从sa[1]开始枚举。这样一定能得到最小的。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;
const int MAXN = 110000;
string s,ss;
int num[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],rk[MAXN],sa[MAXN],height[MAXN];
int minl[MAXN][20],n,a[MAXN],top;
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}

void SA(int *r,int n)
{
    int *x=wa,*y=wb,m=0;
    for (int i=0;i<n;i++) m=max(m,r[i]+1);
    for (int i=0;i<m;i++) wd[i]=0;
    for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
    for (int i=1;i<m;i++) wd[i]+=wd[i-1];
    for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
    int p=1;
    for (int j=1;p<n;j<<=1,m=p)
    {
        p=0;
        for (int i=n-j;i<n;i++) y[p++]=i;
        for (int i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
        for (int i=0;i<n;i++) wv[i]=x[y[i]];
        for (int i=0;i<m;i++) wd[i]=0;
        for (int i=0;i<n;i++) ++wd[wv[i]];
        for (int i=1;i<m;i++) wd[i]+=wd[i-1];
        for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
        swap(x,y);x[sa[0]]=0;p=1;
        for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    for (int i=1;i<n;i++) rk[sa[i]]=i;
    int k=0;
    for (int i=0;i<n-1;height[rk[i++]]=k)
    {
        if (k)--k;
        for (int j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
    }
}

void initRMQ()
{
    int l=(int)(log(double(n))/log(2.0));
    for (int i=1;i<=n;i++) minl[i][0]=height[i];
    for (int j=1;j<=l;j++)
        for (int i=1;i+(1<<(j-1))<=n;i++)
            minl[i][j] = min(minl[i][j-1],minl[i+(1<<(j-1))][j-1]);
}

int askRMQ(int l,int r)
{
    int k=int(log(r-l+1)/log(2.0));
    int a1 = min(minl[l][k],minl[r-(1<<k)+1][k]);
    return a1;
}

int lcp(int a,int b)
{
    a=rk[a],b=rk[b];
    if (a>b) swap(a,b);
    return askRMQ(a+1,b);
}

int main()
{
    int Case = 0;
    while (cin>>s && s!="#")
    {
        ss.clear();
        n=0;
        int len=s.size();
        for (int i=0;i<len;i++) num[n++] = s[i];
        num[n] = 0;
        SA(num,n+1);
        initRMQ();
        int ans=-1,anslen;
        for (int l=1;l<=len;l++)
        {
            for (int pos=0;pos+l<n;pos+=l)
            {
                int tmp = lcp(pos,pos+l);
                int cnt = tmp / l + 1;
                if (tmp%l)
                {
                    int rest =l-tmp%l;
                    if (rest>=0)
                    if (lcp(pos-rest,pos-rest+l) > rest) cnt++;
                }
                if (cnt > ans)
                {
                    ans=cnt;
                    top = 0;
                    a[top++]=l;
                }
                else if (cnt == ans)
                    a[top++]=l;
            }
        }
        bool flag=false;
        for (int i=1;i<=n && !flag;i++)
        {
            for (int j=0;j<top && !flag;j++)
            {
                if (lcp(sa[i],sa[i]+a[j]) >= (ans-1)*a[j])
                {
                    ss = s.substr(sa[i],ans*a[j]);
                    flag=true;
                }
            }
        }
        cout<<"Case "<<++Case<<": "<<ss<<endl;
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/z631681297/article/details/78057103