后缀数组(倍增)

后缀数组的原理大致了解,至于那精简的代码却只能说知道每一步在做什么>_<

(ps:目前已知的文艺程序的代表作)

运用倍增的思想,每个点向后延伸2^i的排名可由2^(i-1)推出,因此用基排每次可在o(n)时间摆平,但是要扩展(logn)次,所以是o(nlogn)的

而height表示相邻两后缀的最长公共前缀,根据定理height[rank[i]]>=height[rank[i-1]]-1

求出height便可以做一系列题目(任意两后缀)

不可重叠最长重复子串(poj1743)

给定一个字符串,求最长重复子串,这两个子串不能重叠。

二分ans,然后按ans分组,每组内的任意两后缀一定能凑出一个重复字串,只需有一组(max and min)差过k,则有解

nlogn

#include <cstdio>
#include <cstdlib>
#include <cstring>
const int oo=1073741819;
int a[200060],b[200060],sa[200060],rk[200060],ht[200060],ws[200060],wv[200060],y[200060],ans,l,n;
void dag(int l,int m)
{
    int i,j,p;
    for (i=1;i<=m;i++) ws[i]=0;
    for (i=1;i<=l;i++) ws[rk[i]=b[i]]++;
    for (i=1;i<=m;i++) ws[i]+=ws[i-1];
    for (i=l;i>=1;i--) sa[ws[rk[i]]--]=i;
    for (j=1,p=0;p<l;j<<=1,m=p)
    {
	for (p=0,i=l-j+1;i<=l;i++) y[++p]=i;
	for (i=1;i<=l;i++) if (sa[i]>j) y[++p]=sa[i]-j;
	for (i=1;i<=l;i++) wv[i]=rk[y[i]];
	for (i=1;i<=m;i++) ws[i]=0;
	for (i=1;i<=l;i++) ws[wv[i]]++;
	for (i=1;i<=m;i++) ws[i]+=ws[i-1];
	for (i=l;i>=1;i--) sa[ws[wv[i]]--]=y[i];
	for (i=1;i<=l;i++) y[i]=rk[i];
	for (rk[sa[1]]=1,p=1,i=2;i<=l;i++)
	    rk[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+j]==y[sa[i-1]+j])) ? p : ++p;
    }
}
void hig(int l)
{
    int i,j,p;
    for (i=1,p=0;i<=l;ht[rk[i++]]=p)
	for (p ? --p : 0,j=sa[rk[i]-1];b[i+p]==b[j+p];++p) ;
}
int check(int k)
{
    int i,min,max;
    for (i=2,min=sa[1],max=sa[1];i<=l;i++)
    {
	if (ht[i]>=k)
	{
	    if (sa[i]<min) min=sa[i];
	    if (sa[i]>max) max=sa[i];
	    if (sa[i-1]<min) min=sa[i-1];
	    if (sa[i-1]>max) max=sa[i-1];
	    if (max-min>k) return 1;
	}
	else {
	    if (max-min>k) return 1;	    
	    else {max=-oo,min=oo;}
	}
    }
    return 0;
}
void make()
{
    int l=1,r=n,mid;
    for (;l<=r;)
    {
	mid=(l+r)>>1;
	if (check(mid))  l=mid+1;
	else r=mid-1;
    }
    ans=l-1;
}
void init()
{
    int i;
    ans=0;
    memset(a,0,sizeof(a));memset(b,0,sizeof(b));
    for (i=1;i<=l;i++) scanf("%d",&a[i]);
    scanf("\n");
    for (i=1;i<l;i++) b[i]=a[i]-a[i+1]+89;
//    b[l]=0;
    l--;n=l;
    dag(l,1000);
    hig(l);
    make();
    if (ans+1>=5) printf("%d\n",ans+1); else printf("0\n");
}
int main()
{
    freopen("1743.in","r",stdin);
    freopen("1743.out","w",stdout);
    for (;;)
    {
	scanf("%d\n",&l);
	if (0==l) break;
	init();
    }
    return 0;
}

最长公共子串(pku2774,ural1517)

给定两个字符串 A 和 B,求最长公共子串。

求出height后,因为对于一个后缀的最长公共前缀,离他越远会越短,所以答案在所有height中最大一个(相邻且分属于不同字符串)

#include <cstdio>
#include <cstdlib>
#include <cstring>
char s[205000],s1[105000],s2[105000];
int wv[205000],ws[205000],y [205000],rk[205000],sa[205000],ht[205000];
int l,ans;
void dag(int l,int m)
{
    int i,j,p;
    for (i=1;i<=m;i++) ws[i]=0;
    for (i=1;i<=l;i++) ws[rk[i]=s[i]]++;
    for (i=1;i<=m;i++) ws[i]+=ws[i-1];
    for (i=l;i>=1;i--) sa[ws[rk[i]]--]=i;
    for (j=1,p=0;p<l;j<<=1,m=p)
    {
        for (p=0,i=l-j+1;i<=l;i++) y[++p]=i;                                         //第二关键字为0 
        for (i=1;i<=l;i++) if (sa[i]>j) y[++p]=sa[i]-j;
        for (i=1;i<=l;i++) wv[i]=rk[y[i]];
        for (i=1;i<=m;i++) ws[i]=0; 
        for (i=1;i<=l;i++) ws[wv[i]]++;
        for (i=1;i<=m;i++) ws[i]+=ws[i-1];
        for (i=l;i>=1;i--) sa[ws[wv[i]]--]=y[i];                                    //wv[i]=rk[y[i]];
        for (i=1;i<=l;i++) y[i]=rk[i];                                              //y[i]=oldrk[i];
        for (rk[sa[1]]=1,i=2,p=1;i<=l;i++)
         rk[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) ? p : ++p;
    }
}
void hig(int l)
{
    int i,j,p=0;
    for (i=1,p=0;i<=l;ht[rk[i++]]=p)                                                //ht[rk[i]]>=ht[rk[i-1]]-1
     for (p?p--:0,j=sa[rk[i]-1];s[j+p]==s[i+p];) 
     p++
     ;                               //ht[1]无用 
}
void init()
{
    int l1,l2,i;
    memset(s1,0,sizeof(s1));
    memset(s2,0,sizeof(s2));
    memset(s ,0,sizeof(s ));    
    scanf("%s\n",s1+1);l1=strlen(s1+1);
    scanf("%s\n",s2+1);l2=strlen(s2+1);
    for (l=1;l<=l1;l++) s[l]=s1[l];
    l=l1+1;s[l]='_';
    for (++l;l<=l1+l2+1;l++) s[l]=s2[l-l1-1];
    s[l]='_';
    dag(l,'z');
    hig(l);
    ans=0;
    for (i=2;i<=l;i++)
    {
        if  (((sa[i]<=l1)&&(sa[i-1]>l1+1))||((sa[i]>l1+1)&&(sa[i-1]<=l1)))
            if (ht[i]>ans) ans=ht[i];
    }
    printf("%d\n",ans);
}
int main()
{
    freopen("2774.in","r",stdin);
    freopen("2774.out","w",stdout);
        init();
    return 0;
}

可重叠的 k 次最长重复子串(pku3261)

给定一个字符串,求至少出现 k 次的最长重复子串,这 k 个子串可以重叠。

二分答案,然后扫描判断。

#include <cstdio>
#include <cstdlib>
#include <cstring>
int ws[1000500],a[50000],y[50000],wv[50000],sa[50000],rk[50000],ht[50000],ans,n,m,st[50000],k;
void dag(int l,int m)
{
    int i,j,p;
    for (i=1;i<=m;i++) ws[i]=0;
    for (i=1;i<=l;i++) ws[rk[i]=a[i]]++;
    for (i=1;i<=m;i++) ws[i]+=ws[i-1];
    for (i=l;i>=1;i--) sa[ws[rk[i]]--]=i;
    for (j=1,p=0;p<l;j<<=1,m=p)
    {
	for (p=0,i=l-j+1;i<=l;i++) y[++p]=i;
	for (i=1;i<=l;i++) if (sa[i]>j) y[++p]=sa[i]-j;
	for (i=1;i<=l;i++) wv[i]=rk[y[i]];
	for (i=1;i<=m;i++) ws[i]=0;
	for (i=1;i<=l;i++) ws[wv[i]]++;
	for (i=1;i<=m;i++) ws[i]+=ws[i-1];
	for (i=l;i>=1;i--) sa[ws[wv[i]]--]=y[i];
	for (i=1;i<=l;i++) y[i]=rk[i];
	for (rk[sa[1]]=1,p=1,i=2;i<=l;i++)
	    rk[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+j]==y[sa[i-1]+j]) ? p : ++p) ;
    }
}
void hig(int l)
{
    int i,j,p;
    for (p=0,i=1;i<=l;ht[rk[i++]]=p)
	for (p ? --p : p,j=sa[rk[i]-1];a[j+p]==a[i+p];++p)  ;                                              //j鏄瘮杈?
}
int check(int p)
{
    int i,j,ll,hh;
    ll=0,hh=0;
    for (i=1,j=1;i<=n;i++)
    {
    	for (;(j+1<=n)&&(j-i+1<k);) 
        {
            j++;
            for (;(ll>hh)&&(st[ll]>ht[j]);ll--) ;
            st[++ll]=ht[j];
        }
    	if (j-i+1<k) return 0;
        if (st[hh+1]>=p) return 1;
        if (st[hh+1]==ht[i+1]) hh++; 
    }
}
void make(int n)
{
    int l=1,r=n,mid;
    for (;l<=r;)
    {
	mid=(l+r)>>1;
	if (check(mid)) l=mid+1;
	else r=mid-1;
    }
    ans=l-1;
}
void init()
{
    int i;
    scanf("%d%d\n",&n,&k);
    for (i=1;i<=n;i++) {scanf("%d\n",&a[i]);a[i]++;}
    dag(n,1000000+5);
    hig(n);
    make(n);
    printf("%d\n",ans);
}
int main()
{
    freopen("3261.in","r",stdin);
    freopen("3261.out","w",stdout);
      init();
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值