后缀数组

在夏令营期间学习了作为一个字符串处理神器的后缀数组。

 

bzoj1031 JSOI字符加密Cipher

题目大意:给一个字符串,圈成圆圈,从任意位置断开,组成len个字符串,按字典序升序排序后,输出尾字母。

思路:将字符串加倍后,对所有后缀排序,用后缀数组的思想,O(nlogn),输出的时候只要输出长度>=len的相应位置的字母就可以了。

#include<iostream>

#include<cstring>

#include<cstdio>

#include<algorithm>

#define maxnode 200005

using namespace std;

int t1[maxnode]={0},t2[maxnode]={0},sa[maxnode]={0},cc[maxnode]={0},n,m=0;

char ss[maxnode];

bool cmp(int *y,int a,int b,int k)

{

    int a2,b2;

    a2= a+k>=n ? -1 : y[a+k];

    b2= b+k>=n ? -1 : y[b+k];

    a=y[a];b=y[b];

    return a==b&&a2==b2;

}

void build()

{

    int i,k,p;int *x=t1;int *y=t2;

    for (i=0;i<m;++i) cc[i]=0;

    for (i=0;i<n;++i) ++cc[x[i]=ss[i]];

    for (i=1;i<m;++i) cc[i]+=cc[i-1];

    for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i;

    for (k=1;k<=n;k<<=1)

    {

        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) cc[i]=0;

        for (i=0;i<n;++i) ++cc[x[y[i]]];

        for (i=1;i<m;++i) cc[i]+=cc[i-1];

        for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i];

        swap(x,y);m=1;x[sa[0]]=0;

        for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++;

        if (m>=n) break;

    }

}

int main()

{

    int i,n1;

    scanf("%s",&ss);n1=strlen(ss);

    for (i=0;i<n1-1;++i) ss[i+n1]=ss[i]; n=n1*2-1;

    for (i=0;i<n1;++i) m=max(m,(int)ss[i]);

    ++m;build();

    for (i=0;i<n;++i)

      if (sa[i]<n1) printf("%c",ss[sa[i]+n1-1]);

    printf("\n");

}
View Code

 

poj2406Power Strings

题目大意:求给定字符串最多能被一个字符串重复几次得到。

思路:虽然在学习后缀数组,不过第一反应是用kmp的思想,用失配数组进行操作。

#include<iostream>

#include<cstring>

#include<algorithm>

#include<cstdio>

#define maxnode 1000005

using namespace std;

char ss[maxnode];

int f[maxnode]={0};

int main()

{

    int i,j,l;

    while(scanf("%s",&ss)==1)

    {

      l=strlen(ss);if (l==1&&ss[0]=='.') break;

      f[0]=f[1]=0;

      for (i=1;i<l;++i)

      {

        j=f[i];

        while(j&&ss[i]!=ss[j]) j=f[j];

        f[i+1]= ss[i]==ss[j] ? j+1 : 0;

      }

      if (f[l]>0&&l%(l-f[l])==0) printf("%d\n",l/(l-f[l]));

      else printf("1\n");

    }

}
View Code

 

poj2774Long Long Message

题目大意:求两个字符串的最长连续公共字串。

思路:将两个字符串s1,s2连在一起,中间用一个特殊的符号连接(我用的是‘$’),求出满足起点在特殊符号两边、并且公共前缀最长的答案就可以了。求这个答案的时候,首先按height建立线段树,然后记录在以排名为下标的sa数组中,每一个串s1对应的后缀的前一个和后一个为串s2对应的后缀的排名,穷举每一个s1串的后缀,然后在线段树中查询之前记录下的min(前+1~rank(i))和min(rank(i)+1~后)中取较小值(这里还要和n-i取较小值),这里一定有最大的最小值在这一前一后之间取得。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define maxnode 200005

#define inf 2100000000LL

using namespace std;

char s1[maxnode],s2[maxnode],ss[maxnode];

int t1[maxnode]={0},t2[maxnode]={0},cc[maxnode]={0},n,m=0,sa[maxnode]={0},rank[maxnode]={0},height[maxnode]={0},tree[maxnode*4]={0},

    ml[maxnode]={0},mr[maxnode]={0};

bool cmp(int *y,int a,int b,int k)

{

    int a2,b2;

    a2= a+k>=n ? -1 : y[a+k];

    b2= b+k>=n ? -1 : y[b+k];

    a=y[a];b=y[b];

    return a==b&&a2==b2;

}

void buildt(int i,int l,int r)

{

    int mid;

    if (l==r){tree[i]=height[l];return;}

    mid=(l+r)/2;buildt(i*2,l,mid);buildt(i*2+1,mid+1,r);

    tree[i]=min(tree[i*2],tree[i*2+1]);

}

int task(int i,int l,int r,int ll,int rr)

{

    int mid,minn=inf;

    if (ll<=l&&r<=rr) return tree[i];  mid=(l+r)/2;

    if (ll<=mid) minn=min(minn,task(i*2,l,mid,ll,rr));

    if (rr>mid) minn=min(minn,task(i*2+1,mid+1,r,ll,rr));

    return minn;

}

void build()

{

    int i,k,p;int *x=t1;int *y=t2;

    for (i=0;i<m;++i) cc[i]=0;

    for (i=0;i<n;++i) ++cc[x[i]=ss[i]];

    for (i=1;i<m;++i) cc[i]+=cc[i-1];

    for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i;

    for (k=1;k<=n;k<<=1)

    {

        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) cc[i]=0;

        for (i=0;i<n;++i) ++cc[x[y[i]]];

        for (i=1;i<m;++i) cc[i]+=cc[i-1];

        for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i];

        swap(x,y);x[sa[0]]=0;m=1;

        for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++;

        if (m>=n) break;

    }

}

void pre()

{

    int i,j,k=0;

    for (i=0;i<n;++i) rank[sa[i]]=i;

    for (i=0;i<n;++i)

    {

        if (!rank[i]) continue;

        if (k) --k; j=sa[rank[i]-1];

        while(ss[i+k]==ss[j+k]) ++k;

        height[rank[i]]=k;

    }

}

int main()

{

    int i,j,n1,n2,ans=0;

    scanf("%s%s",&s1,&s2);n1=strlen(s1);n2=strlen(s2);n=n1+n2+1;

    for (i=0;i<n1;++i) ss[i]=s1[i]; ss[n1]='$';

    for (i=n1+1;i<n;++i) ss[i]=s2[i-n1-1];

    for (i=0;i<n;++i) m=max(m,(int)ss[i]);

    ++m;build();pre();buildt(1,0,n-1);

    j=-1;

    for (i=0;i<n;++i)

    {

        if (sa[i]>n1) j=i;

        else ml[sa[i]]=j;

    }

    j=-1;

    for (i=n-1;i>=0;--i)

    {

        if (sa[i]>n1) j=i;

        else mr[sa[i]]=j;

    }

    for (i=0;i<n1;++i)

    {

        if (n1-i<=ans) break;

        if (ml[i]!=-1) ans=max(ans,min(n1-i,task(1,0,n-1,ml[i]+1,rank[i])));

        if (mr[i]!=-1) ans=max(ans,min(n1-i,task(1,0,n-1,rank[i]+1,mr[i])));

    }

    printf("%d\n",ans);

}
View Code

 其实完全没必要再这么麻烦,我们只要从头扫一遍height数组,找到满足要求的最大的值就可以了。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define maxnode 200005

#define inf 2100000000LL

using namespace std;

char s1[maxnode],s2[maxnode],ss[maxnode];

int t1[maxnode]={0},t2[maxnode]={0},cc[maxnode]={0},n,m=0,sa[maxnode]={0},rank[maxnode]={0},height[maxnode]={0};

bool cmp(int *y,int a,int b,int k)

{

    int a2,b2;

    a2= a+k>=n ? -1 : y[a+k];

    b2= b+k>=n ? -1 : y[b+k];

    a=y[a];b=y[b];

    return a==b&&a2==b2;

}

void build()

{

    int i,k,p;int *x=t1;int *y=t2;

    for (i=0;i<m;++i) cc[i]=0;

    for (i=0;i<n;++i) ++cc[x[i]=ss[i]];

    for (i=1;i<m;++i) cc[i]+=cc[i-1];

    for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i;

    for (k=1;k<=n;k<<=1)

    {

        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) cc[i]=0;

        for (i=0;i<n;++i) ++cc[x[y[i]]];

        for (i=1;i<m;++i) cc[i]+=cc[i-1];

        for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i];

        swap(x,y);x[sa[0]]=0;m=1;

        for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++;

        if (m>=n) break;

    }

}

void pre()

{

    int i,j,k=0;

    for (i=0;i<n;++i) rank[sa[i]]=i;

    for (i=0;i<n;++i)

    {

        if (!rank[i]) continue;

        if (k) --k; j=sa[rank[i]-1];

        while(ss[i+k]==ss[j+k]) ++k;

        height[rank[i]]=k;

    }

}

int main()

{

    int i,j,n1,n2,ans=0;

    scanf("%s%s",&s1,&s2);n1=strlen(s1);n2=strlen(s2);n=n1+n2+1;

    for (i=0;i<n1;++i) ss[i]=s1[i]; ss[n1]='$';

    for (i=n1+1;i<n;++i) ss[i]=s2[i-n1-1];

    for (i=0;i<n;++i) m=max(m,(int)ss[i]);

    ++m;build();pre();

    for (i=1;i<n;++i)

      if ((sa[i-1]<n1&&sa[i]>n1)||(sa[i-1]>n1&&sa[i]<n1)) ans=max(ans,height[i]);

    printf("%d\n",ans);

}
View Code

 

bzoj3238差异

题目大意:求sigma(lenTi+lenTj-2*lcp(Ti,Tj)),1<=i<j<=n,Ti表示从第i个字符开始的后缀,小标从1开始。

思路:将sigma拆开,可以发现主要就是求2*lcp(Ti,Tj),先用后缀数组构建height,然后建立线段树,保存最小值和最小值的位置,分治处理,对区间[l,r],找线段树中[l+1,r](下表一定注意,因为height[i]表示i和i-1的lcp长度)的最小值minn和位置minp,给ans减去(minn*(minp-l)*(r-minp+1))(还是要十分注意下标),然后在把sigma中其他部分加起来就行了。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define maxnode 500005

#define inf 2100000000LL

#define LL long long

using namespace std;

struct use{

    int minn,minp;

}tree[maxnode*4]={0};

char ss[maxnode]={0};

int t1[maxnode]={0},t2[maxnode]={0},cc[maxnode]={0},sa[maxnode]={0},rank[maxnode]={0},height[maxnode]={0},n,m=0;

long long ans=0;

bool cmp(int *y,int a,int b,int k)

{

    int a2,b2;

    a2= a+k>=n ? -1 : y[a+k];

    b2= b+k>=n ? -1 : y[b+k];

    a=y[a];b=y[b];

    return a==b&&a2==b2;

}

void build()

{

    int i,k,p; int *x=t1;int *y=t2;

    for (i=0;i<m;++i) cc[i]=0;

    for (i=0;i<n;++i) ++cc[x[i]=ss[i]];

    for (i=1;i<m;++i) cc[i]+=cc[i-1];

    for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i;

    for (k=1;k<=n;k<<=1)

    {

        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) cc[i]=0;

        for (i=0;i<n;++i) ++cc[x[y[i]]];

        for (i=1;i<m;++i) cc[i]+=cc[i-1];

        for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i];

        swap(x,y);m=1;x[sa[0]]=0;

        for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++;

        if (m>=n) break;

    }

}

void pre()

{

    int i,j,k=0;

    for (i=0;i<n;++i) rank[sa[i]]=i;

    for (i=0;i<n;++i)

    {

        if (!rank[i]) continue;

        if (k) --k; j=sa[rank[i]-1];

        while(ss[i+k]==ss[j+k]) ++k;

        height[rank[i]]=k;

    }

}

use updata(use x,use y){return x.minn<=y.minn ? x : y;}

void buildt(int i,int l,int r)

{

    int mid;

    if (l==r){tree[i].minn=height[l];tree[i].minp=l;return;};

    mid=(l+r)/2;

    buildt(i*2,l,mid);buildt(i*2+1,mid+1,r);

    tree[i]=updata(tree[i*2],tree[i*2+1]);

}

use task(int i,int l,int r,int ll,int rr)

{

    use x1,x2; int mid;

    if (ll<=l&&r<=rr) return tree[i];

    mid=(l+r)/2;x1.minn=x2.minn=inf;

    if (ll<=mid) x1=task(i*2,l,mid,ll,rr);

    if (rr>mid) x2=task(i*2+1,mid+1,r,ll,rr);

    return updata(x1,x2);

}

void work(int l,int r)

{

    use xx;

    if (l>=r) return;

    xx=task(1,1,n-1,l+1,r);

    work(l,xx.minp-1);work(xx.minp,r);

    ans-=2*(LL)xx.minn*(LL)(xx.minp-l)*(LL)(r-xx.minp+1);

}

int main()

{

    int i,j;

    scanf("%s",&ss);n=strlen(ss);

    for (i=0;i<n;++i) m=max(m,(int)ss[i]);

    ++m;build();pre();buildt(1,1,n-1);

    for (i=0;i<n;++i) ans+=(LL)(n-1)*(LL)(n-i);

    work(0,n-1);printf("%lld\n",ans);

}
View Code

 

你可能感兴趣的:(后缀数组)