后缀数组

后缀数组

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

 

bzoj2251 外星联络

题目大意:求出一个01串中出现次数多于一次的字串的个数(按字典序排序)。

思路:用后缀数组做出height后,所有后缀都排好序了,那么靠前面并且长度小的就是字典序小的。但是这里要保证不重复并且超过两次出现,所以我们每次从这一位的height[i]+1的长度到这个后缀长度开始往后找,只要height[j]>height[i]+1就一直往后,这样保证了开头是单调递增的长度也单调,所以答案就是字典序排好的了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 3005
using namespace std;
int sa[maxnode],t1[maxnode],t2[maxnode],c[maxnode],n,m,height[maxnode]={0},rank[maxnode]={0};
char s[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 *x=t1,*y=t2,i,k,p;
    for (i=0;i<=m;++i) c[i]=0;
    for (i=0;i<n;++i) ++c[x[i]=(s[i]-'0')];
    for (i=1;i<=m;++i) c[i]+=c[i-1];
    for (i=n-1;i>=0;--i) sa[--c[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) c[i]=0;
        for (i=0;i<n;++i) ++c[x[y[i]]];
        for (i=1;i<=m;++i) c[i]+=c[i-1];
        for (i=n-1;i>=0;--i) sa[--c[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 k=0,i,j;
    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(s[i+k]==s[j+k]) ++k;
        height[rank[i]]=k;
    }
}
int main()
{
    int i,j,k;
    scanf("%d",&n);m=1;
    for (i=0;i<n;++i)
    {
        while(scanf("%c",&s[i])==1)
          if (s[i]>='0'&&s[i]<='1') break;
    }
    build();pre();
    for (i=0;i<n;++i)
    {
        for (j=height[i]+1;j+sa[i]<=n;++j)
        {
            for (k=i+1;k<n&&height[k]>=j;++k);
            if (k-i>1) printf("%d\n",k-i);
        }
    }
}
View Code

 

bzoj3879 SvT

题目大意:给定一个字符串,多组询问,每次问某些后缀两两之间lcp的长度和。

思路:对字符串建后缀数组,对于每组询问,按rank排序后,求出相邻两个的lcp,对这个排序之后,类似差异的做法,用这个最小值更新lcp也是这个的答案。

注意:st表预处理的时候,i后面2^j个的时候,要判断i+2^j是否小于n,否则会re。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500005
#define M 3000005
#define up 20
#define inf 2100000000
#define LL long long
#define p 23333333333333333LL
using namespace std;
char s[N];
struct tree{
    int mx,mn,del;
    void init(){mx=-inf;mn=inf;del=1;}
}tr[N<<2];
struct use{
    int mn,po;
    bool operator<(const use&x)const{return mn<x.mn;}
}ai[M];
int x1[N],x2[N],n,m,sa[N],rank[N],height[N]={0},ci[N],cc[N],lo[N],mn[N][up];
int cmp(int *y,int a,int b,int k){
    int aa,bb;
    aa=(a+k>=n ? -1 : y[a+k]);
    bb=(b+k>=n ? -1 : y[b+k]);
    a=y[a];b=y[b];
    return a==b&&aa==bb;}
int mcm(int x,int y){return rank[x]<rank[y];}
void getsa(){
    int i,k,pp;int *x=x1;int *y=x2;
    for (i=0;i<m;++i) cc[i]=0;
    for (i=0;i<n;++i) ++cc[x[i]=s[i]-'a'];
    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){
        for (pp=0,i=n-k;i<n;++i) y[pp++]=i;
        for (i=0;i<n;++i) if (sa[i]>=k) y[pp++]=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(s[j+k]==s[i+k]) ++k;
        height[rank[i]]=k;
    }memset(mn,127/3,sizeof(mn));
    for (i=0;i<n;++i) mn[i][0]=height[i];
    for (i=1;(1<<i)<=n;++i){
        for (j=0;j+(1<<(i-1))<n;++j)
            mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);
    }
    for (k=0,i=1;i<=n;++i){
        if ((1<<(k+1))<=i) ++k;
        lo[i]=k;
    }
}
void pushdown(int i){
    tr[i<<1].mx=tr[i<<1|1].mx=-inf;
    tr[i<<1].mn=tr[i<<1|1].mn=inf;
    tr[i<<1].del^=1;tr[i<<1|1].del^=1;
    tr[i].del=0;}
tree updata(tree x,tree y){
    tree c;c.del=0;
    c.mx=max(x.mx,y.mx);
    c.mn=min(x.mn,y.mn);
    return c;}
void ins(int i,int l,int r,int x){
    if (l==r){tr[i]=(tree){l,l,0};return;}
    int mid=(l+r)>>1;
    if (tr[i].del) pushdown(i);
    if (x<=mid) ins(i<<1,l,mid,x);
    else ins(i<<1|1,mid+1,r,x);
    tr[i]=updata(tr[i<<1],tr[i<<1|1]);}
int amx(int i,int l,int r,int ll,int rr){
    if (ll<=l&&r<=rr) return tr[i].mx;
    int mid=(l+r)>>1;int mm=-inf;
    if (tr[i].del) pushdown(i);
    if (ll<=mid) mm=max(mm,amx(i<<1,l,mid,ll,rr));
    if (rr>mid) mm=max(mm,amx(i<<1|1,mid+1,r,ll,rr));
    return mm;}
int amn(int i,int l,int r,int ll,int rr){
    if (ll<=l&&r<=rr) return tr[i].mn;
    int mid=(l+r)>>1;int mm=inf;
    if (tr[i].del) pushdown(i);
    if (ll<=mid) mm=min(mm,amn(i<<1,l,mid,ll,rr));
    if (rr>mid) mm=min(mm,amn(i<<1|1,mid+1,r,ll,rr));
    return mm;}
int getmn(int l,int r){
    int x=lo[r-l];++l;
    return min(mn[l][x],mn[r-(1<<x)+1][x]);}
int main(){
    int i,j,q,t,l,r;LL ans;scanf("%d%d",&n,&q);
    scanf("%s",s);m=26;
    getsa();pre();
    while(q--){
        scanf("%d",&t);
        for (i=1;i<=t;++i){scanf("%d",&ci[i]);--ci[i];}
        sort(ci+1,ci+t+1,mcm);t=unique(ci+1,ci+t+1)-ci-1;
        for (i=2;i<=t;++i)
            ai[i]=(use){getmn(rank[ci[i-1]],rank[ci[i]]),i};
        sort(ai+2,ai+t+1);ans=0LL;
        tr[1].init();
        ins(1,1,t+1,1);ins(1,1,t+1,t+1);
        for (i=2;i<=t;++i){
            j=ai[i].po;
            l=amx(1,1,t+1,1,j-1);
            r=amn(1,1,t+1,j+1,t+1);
            ans=(ans+(LL)ai[i].mn*(LL)(j-l)*(LL)(r-j)%p)%p;
            ins(1,1,t+1,j);
        }printf("%I64d\n",ans);
    }
}
View Code

 

bzoj4453 cys就是要拿英魂!(!!!

题目大意:给定一个字符串,每次询问一个区间内的最大子串。

思路:这个子串肯定是这个区间的一个后缀,所以考虑求一个区间最大后缀。如果按照左端点从大到小扫的话,每个点处取得最大值的位置是单调不减的,所以可以用一个栈维护这样的决策序列,先弹掉整个区间都不优的,之后从那个可能优的区间里二分,就是这个点能影响到的范围,比较两个后缀大小就是比较lcp之后的那个字母(如果后缀的lcp超过短串的长度,就是长串更长),所以可以用后缀数组预处理。

注意:后缀数组求lcp时下标是rank。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define up 20
#define inf 2100000000
using namespace std;
struct use{
    int x,l,r;
    bool operator<(const use&x)const{return l>x.l;}
}ai[N],zh[N];
char ss[N];
int zt=0,lo[N],st[N][up],sa[N],height[N],rank[N],x1[N],x2[N],cc[N],tr[N<<2],del[N<<2];
int in(){
    char ch=getchar();int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';ch=getchar();
    }return x;}
int cmp(int n,int *y,int a,int b,int k){
    int aa,bb;
    aa=(a+k>=n ? -1 : y[a+k]);
    bb=(b+k>=n ? -1 : y[b+k]);
    a=y[a];b=y[b];
    return (a==b&&aa==bb);}
void pre(int n,int m){
    int i,j,k,p;int *x=x1;int *y=x2;
    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){
        for (p=0,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(n,y,sa[i],sa[i-1],k) ? m-1 : m++);
        if (m>=n) break;
    }for (i=0;i<n;++i) rank[sa[i]]=i;
    height[rank[0]]=k=0;
    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;
    }for (i=0;i<n;++i) st[i][0]=height[i];
    for (j=1;(1<<j)<=n;++j)
        for (i=0;i+(1<<(j-1))<n;++i)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    for (j=0,i=1;i<=n;++i){
        if ((1<<(j+1))<=i) ++j;
        lo[i]=j;
    }
}
int lcp(int l,int r){
    l=rank[l];r=rank[r];
    if (l>r) swap(l,r);
    int d=lo[r-l];++l;
    return min(st[l][d],st[r-(1<<d)+1][d]);}
bool mcm(int x,int y,int r){
    int lc=lcp(x,y);
    if (y+lc>r) return true;
    return ss[x+lc]>ss[y+lc];}
void pushdown(int i){
    del[i<<1]=del[i<<1|1]=del[i];
    tr[i<<1]=tr[i<<1|1]=del[i];
    del[i]=-1;}
void tch(int i,int l,int r,int ll,int rr,int x){
    if (ll<=l&&r<=rr){tr[i]=del[i]=x;return;}
    int mid=(l+r)>>1;
    if (del[i]>=0) pushdown(i);
    if (ll<=mid) tch(i<<1,l,mid,ll,rr,x);
    if (rr>mid) tch(i<<1|1,mid+1,r,ll,rr,x);}
int geta(int i,int l,int r,int x){
    if (l==r) return tr[i];
    int mid=(l+r)>>1;
    if (del[i]>=0) pushdown(i);
    if (x<=mid) return geta(i<<1,l,mid,x);
    else return geta(i<<1|1,mid+1,r,x);}
int gete(int x,use y){
    int l,r,mid,ans=y.l-1;
    l=y.l;r=y.r;
    while(l<=r){
        mid=(l+r)>>1;
        if (mcm(x,y.x,mid)){l=mid+1;ans=mid;}
        else r=mid-1;
    }return ans;}
void getz(int l,int x){
    while(zt){
        if (mcm(x,zh[zt].x,zh[zt].r)) --zt;
        else break;
    }if (!zt){
        zh[++zt]=(use){x,x,zh[0].r-1};
        tch(1,0,l-1,x,l-1,x);
    }else{
        int r=gete(x,zh[zt]);
        zh[zt].l=r+1;
        zh[++zt]=(use){x,x,r};
        tch(1,0,l-1,x,r,x);
    }
}
int main(){
    int i,j,l,n,m=0;
    scanf("%s",ss);l=strlen(ss);
    for (i=0;i<l;++i) m=max(m,ss[i]+1);
    for (pre(l,m),n=in(),i=1;i<=n;++i)
        ai[i]=(use){i,in()-1,in()-1};
    sort(ai+1,ai+n+1);
    zh[0]=(use){0,0,l};
    memset(del,-60,sizeof(del));
    for (j=1,i=l-1;i>=0;--i){
        getz(l,i);
        for (;j<=n&&ai[j].l==i;++j)
            x1[ai[j].x]=geta(1,0,l-1,ai[j].r);
    }for (i=1;i<=n;++i) printf("%d\n",x1[i]+1);
}
View Code

 

bzoj4556 字符串

题目大意:给定一个字符串s,m组询问:s[a...b]的子串和s[c...d]的最长公共前缀。

思路:用后缀数组求出height之后,二分答案,每次找height满足要求的区间内找有没有在[a,b-mid+1]范围内的,用主席树(第一层是rank,第二层是位置)查询。

注意:1)不要读错题;

     2)如果找rank的前驱后继,可能会出现公共前缀很长,但是被b这个位置截掉的情况。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define up 20
#define M 2000005
using namespace std;
int in(){
    char ch=getchar();int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';ch=getchar();
    }return x;}
struct use{int l,r,sm;}tr[M];
int n,m,sa[N],rank[N],height[N],ci[N],x1[N],x2[N],lo[N],di[up][N],rt[N]={0},tt=0;
char ss[N];
bool cmp(int *y,int a,int b,int k){
    int aa,bb;
    aa=(a+k>=n ? -1 : y[a+k]);
    bb=(b+k>=n ? -1 : y[b+k]);
    a=y[a];b=y[b];
    return a==b&&aa==bb;}
void pre(){
    int i,j,k,p;m=26;int *x=x1;int *y=x2;
    for (i=0;i<m;++i) ci[i]=0;
    for (i=0;i<n;++i) ++ci[x[i]=(ss[i]-'a')];
    for (i=1;i<m;++i) ci[i]+=ci[i-1];
    for (i=n-1;i>=0;--i) sa[--ci[x[i]]]=i;
    for (k=1;k<=n;k<<=1){
        for (p=0,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) ci[i]=0;
        for (i=0;i<n;++i) ++ci[x[y[i]]];
        for (i=1;i<m;++i) ci[i]+=ci[i-1];
        for (i=n-1;i>=0;--i) sa[--ci[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;
    }for (i=0;i<n;++i) rank[sa[i]]=i;
    for (k=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;
    }for (i=0;i<n;++i) di[0][i]=height[i];
    for (i=1;(1<<i)<=n;++i)
        for (j=0;j+(1<<(i-1))<n;++j)
            di[i][j]=min(di[i-1][j],di[i-1][j+(1<<(i-1))]);
    for (k=0,i=1;i<=n;++i){
        if ((1<<(k+1))<=i) ++k;
        lo[i]=k;
    }
}
int amn(int x,int y){
    if (x==y) return (n-sa[x]);
    if (x>y) swap(x,y);
    int k=lo[y-x];++x;
    return min(di[k][x],di[k][y-(1<<k)+1]);
}
void ins(int &i,int la,int l,int r,int x){
    tr[i=++tt]=tr[la];++tr[i].sm;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) ins(tr[i].l,tr[la].l,l,mid,x);
    else ins(tr[i].r,tr[la].r,mid+1,r,x);
}
int ask(int i,int j,int l,int r,int ll,int rr){
    if (!(tr[j].sm-tr[i].sm)) return 0;
    if (ll<=l&&r<=rr) return tr[j].sm-tr[i].sm;
    int sm=0,mid=(l+r)>>1;
    if (ll<=mid) sm+=ask(tr[i].l,tr[j].l,l,mid,ll,rr);
    if (rr>mid) sm+=ask(tr[i].r,tr[j].r,mid+1,r,ll,rr);
    return sm;}
int getl(int c,int x){
    int l,r,mid,ans;
    l=1;r=rank[c-1];ans=r+1;
    while(l<=r){
        mid=(l+r)>>1;
        if (amn(mid-1,rank[c-1])>=x){r=mid-1;ans=mid;}
        else l=mid+1;
    }return ans-1;
}
int getr(int c,int x){
    int l,r,mid,ans;
    l=rank[c-1]+1;r=n-1;ans=l-1;
    while(l<=r){
        mid=(l+r)>>1;
        if (amn(rank[c-1],mid)>=x){l=mid+1;ans=mid;}
        else r=mid-1;
    }return ans;
}
int query(int a,int b,int c,int d){
    int l,r,le,re,mid,mx=0;
    l=0;r=min(d-c+1,b-a+1);
    while(l<=r){
        mid=(l+r)>>1;
        le=getl(c,mid);
        re=getr(c,mid);
        if (ask(rt[le],rt[re+1],1,n,a,b-mid+1)){l=mid+1;mx=mid;}
        else r=mid-1;
    }return mx;
}
int main(){
    int q,i,a,b,c,d;
    n=in();q=in();
    scanf("%s",ss);pre();
    for (i=1;i<=n;++i)
        ins(rt[i],rt[i-1],1,n,sa[i-1]+1);
    for (i=1;i<=q;++i){
        a=in();b=in();c=in();d=in();
        printf("%d\n",query(a,b,c,d));
    }
}
View Code

 

bzoj2119 股市的预测(!!!

题目大意:求隔过长度为m-1的段后两边(不一定是全部)的升降幅度一样的子串个数。

思路:差分之后就是求ABA的形式,其中B的长度为m的个数。枚举A的长度i,每i个点的位置为j,j和i+m+j比较一下求出向前向后最多能扩展的长度x,如果长度>=i就可以累加答案x-i+1,注意这里x是在这个j的范围内,不能超过j-i+1和j+i,否则会重复统计。

orz vaorz vaorz va

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define up 17
#define LL long long
using namespace std;
int in(){
    char ch=getchar();int x=0,f=1;
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-'){f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';ch=getchar();
    }return x*f;}
int cz=0,ai[N],x1[N],x2[N],sa[N],rank[N],height[N]={0},cc[N],st[up][N],lo[N];
LL ci[N];
int cmp(int n,int *y,int a,int b,int k){
    int aa,bb;
    aa=(a+k>=n ? -1 : y[a+k]);
    bb=(b+k>=n ? -1 : y[b+k]);
    a=y[a];b=y[b];
    return a==b&&aa==bb;}
void pre(int n,int m){
    int i,j,p,k;int *x=x1;int *y=x2;
    for (i=0;i<m;++i) cc[i]=0;
    for (i=0;i<n;++i) ++cc[x[i]=ai[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){
        for (p=0,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(n,y,sa[i],sa[i-1],k) ? m-1 : m++);
        if (m>=n) break;
    }for (i=0;i<n;++i) rank[sa[i]]=i;
    for (k=i=0;i<n;++i){
        if (!rank[i]) continue;
        if (k) --k;j=sa[rank[i]-1];
        for (;ai[i+k]==ai[j+k];++k);
        height[rank[i]]=k;
    }for (i=0;i<n;++i) st[0][i]=height[i];
    for (i=1;(1<<i)<=n;++i)
        for (j=0;j+(1<<(i-1))<n;++j)
            st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
    for (k=0,i=1;i<=n;++i){
        if ((1<<(k+1))<=i) ++k;
        lo[i]=k;
    }
}
int amn(int x,int y){
    if (x>y) swap(x,y);
    int k=lo[y-x];++x;
    return min(st[k][x],st[k][y-(1<<k)+1]);
}
int main(){
    int i,j,a,b,n,m;LL ans=0LL;n=in();m=in();
    for (i=1;i<=n;++i){
        ai[i]=in();
        if (i>1) ci[++cz]=(LL)ai[i]-ai[i-1];
    }sort(ci+1,ci+cz+1);
    cz=unique(ci+1,ci+cz+1)-ci-1;
    for (--n,i=1;i<=n;++i) ai[i-1]=upper_bound(ci+1,ci+cz+1,(LL)ai[i+1]-ai[i])-ci-2;
    ai[n]=cz;++cz;
    for (i=1;i<=n;++i) ai[n*2+1-i]=ai[i-1];
    pre(n*2+1,cz);
    for (i=1;i+i+m<=n;++i)
        for (a=0,j=0;j+i+m<n;j+=i){
            b=min(i,amn(rank[j],rank[j+i+m]));
            if (a+b>=i) ans+=(LL)(a+b-i+1);
            a=min(i-1,amn(rank[n*2-(i+j-1)],rank[n*2-(i+i+j+m-1)]));
        }
    printf("%I64d\n",ans);
}
View Code

 

bzoj4566 找相同字符

题目大意:求a和b串的公共子串个数(位置不同的算不同的)。

思路:中间加一个不再字符集的字母接起来。和差异一样,只是统计答案的时候是左右边分别属于a、b的个数的乘积。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400005
#define LL long long
using namespace std;
struct use{int mn,po;}tr[N<<2],ci;
int n=0,n1,sa[N],rank[N],height[N],x1[N],x2[N],cc[N],seg[N<<2];
LL ans=0LL;
char ss[N];
void in(){
    char ch=getchar();
    while(ch<'a'||ch>'z') ch=getchar();
    while(ch>='a'&&ch<='z'){
        ss[n++]=ch;ch=getchar();
    }
}
int cmp(int *y,int a,int b,int k){
    int aa,bb;
    aa=(a+k>=n ? -1 : y[a+k]);
    bb=(b+k>=n ? -1 : y[b+k]);
    a=y[a];b=y[b];
    return (a==b&&aa==bb);
}
void pre(){
    int i,j,k,p,m;int *x=x1;int *y=x2;
    for (m=27,i=0;i<m;++i) cc[i]=0;
    for (i=0;i<n;++i) ++cc[x[i]=ss[i]-'a'];
    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){
        for (p=0,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;
    }for (i=0;i<n;++i) rank[sa[i]]=i;
    for (k=0,i=0;i<n;++i){
        if (!rank[i]) continue;
        if (k) --k;j=sa[rank[i]-1];
        for (;ss[i+k]==ss[j+k];++k);
        height[rank[i]]=k;
    }
}
use updata(use x,use y){
    use c;
    if (x.mn<=y.mn) c=x;
    else c=y;
    return c;}
void build(int i,int l,int r){
    if (l==r){tr[i]=(use){height[l],l};return;}
    int mid=(l+r)>>1;
    build(i<<1,l,mid);build(i<<1|1,mid+1,r);
    tr[i]=updata(tr[i<<1],tr[i<<1|1]);
}
void buils(int i,int l,int r){
    if (l==r){seg[i]=(sa[l]<n1);return;}
    int mid=(l+r)>>1;
    buils(i<<1,l,mid);buils(i<<1|1,mid+1,r);
    seg[i]=seg[i<<1]+seg[i<<1|1];
}
void amn(int i,int l,int r,int ll,int rr){
    if (ll<=l&&r<=rr){
        if (l==ll) ci=tr[i];
        else ci=updata(ci,tr[i]);
        return;
    }int mid=(l+r)>>1;
    if (ll<=mid) amn(i<<1,l,mid,ll,rr);
    if (rr>mid) amn(i<<1|1,mid+1,r,ll,rr);
}
int ask(int i,int l,int r,int ll,int rr){
    if (ll<=l&&r<=rr) return seg[i];
    int sm=0,mid=(l+r)>>1;
    if (ll<=mid) sm+=ask(i<<1,l,mid,ll,rr);
    if (rr>mid) sm+=ask(i<<1|1,mid+1,r,ll,rr);
    return sm;
}
void work(int l,int r){
    if (l>=r) return;
    amn(1,1,n-1,l+1,r);
    int s1,s2;
    s1=ask(1,0,n-1,l,ci.po-1);
    s2=ask(1,0,n-1,ci.po,r);
    ans+=(LL)ci.mn*((LL)s1*(LL)(r-ci.po+1-s2)+(LL)(ci.po-l-s1)*(LL)s2);
    s1=ci.po;work(l,s1-1);work(s1,r);
}
int main(){
    in();n1=n;ss[n++]='z'+1;
    in();pre();
    build(1,1,n-1);buils(1,0,n-1);
    work(0,n-1);
    printf("%I64d\n",ans);
}
View Code

 

后缀平衡树

bzoj2555

题目大意:维护一个字符串,支持:(1)在末尾加一段字符;(2)查询一个字符串出现的次数。

思路:维护前缀,用平衡树动态加入,比较的时候可以用二分+hash求出lcp,然后比较大小。查询x的次数的时候相当于<xS - <x的(S是一个比字符集大的字符)。

注意:卡常技巧:(1)用unsigned int比unsigned long long快;(2)二分时定一个参数,如果参数长度的不同就把上界设为参数(参数取得25)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 3000005
#define p 569LL
#define up 26
#define UL unsigned int
#define ji 25
using namespace std;
UL ha[N],mi[N],ha1[N];
int le=0,l1,qt=0;
char ss[N],s1[N];
inline UL geth(int k,int l,int r){
    if (k) return (ha1[r]-(l-1>=0 ? ha1[l-1] : 0LL))*mi[N-r-1];
    else return (ha[r]-(l-1>=0 ? ha[l-1] : 0LL))*mi[N-r-1];}
struct node{
    node *ch[2];
    int po,r,sz;
    int cmp(int k){
        char *s=(k ? s1 : ss);
        int l,r,mid,lc,len=(k ? l1 : le);
        lc=l=0;
        if (min(po,len)<=ji||(geth(0,po-ji,po-1)==geth(k,len-ji,len-1))) r=min(po,len);
        else r=ji;
        while(l<=r){
            mid=(l+r)>>1;
            if (geth(0,po-mid,po-1)==geth(k,len-mid,len-1)){lc=mid;l=mid+1;}
            else r=mid-1;
        }if (po==len&&lc==len) return -1;
        if (lc==po) return 1;
        if (lc==len) return 0;
        return (ss[po-lc-1]<s[len-lc-1]);
    }
    inline void updata(){
        sz=1;
        if (ch[0]!=NULL) sz+=ch[0]->sz;
        if (ch[1]!=NULL) sz+=ch[1]->sz;
    }
}*rt,que[N*3];
inline void in(){
    l1=0;char ch=getchar();
    while(ch<'A'||ch>'Z') ch=getchar();
    while(ch>='A'&&ch<='Z'){
        s1[l1++]=ch;ch=getchar();
    }
}
inline void decode(int mask){
    for (int i=0;i<l1;++i){
        mask=(mask*131+i)%l1;
        swap(s1[i],s1[mask]);
    }
}
inline void rotate(node* &o,int d){
    node *k=o->ch[d^1];o->ch[d^1]=k->ch[d];k->ch[d]=o;
    o->updata();k->updata();o=k;}
void ins(node* &o){
    if (o==NULL){
        o=&que[qt++];
        o->po=le;o->r=rand();
        o->ch[0]=o->ch[1]=NULL;
        o->sz=1;
    }else{
        int d=o->cmp(0);ins(o->ch[d]);
        if (o->ch[d]->r > o->r) rotate(o,d^1);
        o->updata();
    }
}
int rank(node *o){
    if (o==NULL) return 0;
    int d=o->cmp(1);
    if (d==-1) return (o->ch[0]==NULL ? 0 : o->ch[0]->sz);
    if (!d) return rank(o->ch[0]);
    else return (o->ch[0]==NULL ? 0 : o->ch[0]->sz)+1+rank(o->ch[1]);
}
int main(){
    int i,j,m,ans,mask=0;
    scanf("%d",&m);in();
    for (mi[0]=1LL,i=1;i<N;++i) mi[i]=mi[i-1]*p;
    for (i=0;i<l1;++i){
        ha[le]=(le ? ha[le-1] : 0LL)+(UL)(s1[i]-'A')*mi[le];
        ss[le++]=s1[i];ins(rt);
    }while(m--){
        in();
        if (s1[0]=='A'){
            in();decode(mask);
            for (i=0;i<l1;++i){
                ha[le]=ha[le-1]+(UL)(s1[i]-'A')*mi[le];
                ss[le++]=s1[i];
                ins(rt);
            }
        }else{
            in();decode(mask);
            for (i=0;i<l1;++i)
                ha1[i]=(i ? ha1[i-1] : 0LL)+(UL)(s1[i]-'A')*mi[i];
            ans=-rank(rt);
            for (i=l1;i;--i) s1[i]=s1[i-1];
            s1[0]=up+'A';++l1;
            for (i=0;i<l1;++i)
                ha1[i]=(i ? ha1[i-1] : 0LL)+(UL)(s1[i]-'A')*mi[i];
            ans+=rank(rt);
            mask^=ans;printf("%d\n",ans);
        }
    }
}
View Code

 

posted @ 2015-07-25 14:33  Rivendell  阅读(365)  评论(0编辑  收藏  举报