http://poj.org/problem?id=1743

题意:给n个数组成的串,求是否有多个“相似”且不重叠的子串的长度大于等于5,两个子串相似当且仅当长度相等且每一位的数字差都相等。

题解:后缀数组+二分+不可重叠最长重复子串

C++版本一

/*
*@Author:   STZG
*@Language: C++
*/
//#include <bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<string>
#include<vector>
#include<bitset>
#include<queue>
#include<deque>
#include<stack>
#include<cmath>
#include<list>
#include<map>
#include<set>
//#define DEBUG
#define RI register int
#define endl "\n"
using namespace std;
typedef long long ll;
//typedef __int128 lll;
const int N=200000+10;
const int M=100000+10;
const int MOD=1e9+7;
const double PI = acos(-1.0);
const double EXP = 1E-8;
const int INF = 0x3f3f3f3f;
int t,n,m,k,p,l,r,u,v;
int ans,cnt,flag,temp,sum;
int a[N],s[N];
int wa[N],wb[N],c[N];
char str;
struct node{};
int cmp(int *r,int a,int b,int l){
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int sa[N],Rank[N],height[N];
void SA(int *r,int n,int m){
    for(int i=0;i<=m;i++)c[i]=0;
    for(int i=1;i<=n;i++)c[wa[i]=r[i]]++;
    for(int i=1;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[wa[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int num=0;
        for(int i=n-k+1;i<=n;i++)wb[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)wb[++num]=sa[i]-k;
        for(int i=0;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[wa[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[wa[wb[i]]]--]=wb[i],wb[i]=0;
        swap(wa,wb);
        wa[sa[1]]=1;
        num=1;
        for(int i=2;i<=n;i++)wa[sa[i]]=cmp(wb,sa[i],sa[i-1],k)?num:++num;
        if(num==n)
            break;
        m=num;

    }
}
void calheight(const int *r,int n)
{
	int i,j,k=0;
	for(i=1;i<=n+1;i++) Rank[sa[i]]=i;
	for(i=1;i<=n;height[Rank[i++]]=k)
		for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);
	return;
}
bool sloved(int mid,int n){
    int maxn=sa[1],minn=sa[1];
    for (int i=2;i<=n;++i){//不管height有多大,我只取mid这么长,因为做差了,所以实际上height[i]对应的是height[i]+1这么长的原序列
        if (height[i]>=mid-1){//LCP(i,k)=min(LCP(i,j),LCP(j,k)) (i<=j<=k)
        //使height始终满足条件,保证上面式子取min始终大于等于mid,因为要保证至少有一处重复
            maxn=max(maxn,sa[i]);
            minn=min(minn,sa[i]);
        }//相邻2个肯定是最相似的,如果相邻2个都小于mid,那么与其他之前的串也不会是大于等于mid了
        else maxn=minn=sa[i];//推过来的过程中有不满足的就断了,也是一个起始点
        if (maxn-minn>=mid){//因为是后缀,小的串是大的串的一部分,所以只要两个串的差大于等于mid就能容下小的串,就不会重叠
        //这里本质上应该是maxn+1-(minn+1)>=mid
            return 1;
        }
    }
    return 0;
}

int main()
{
#ifdef DEBUG
	freopen("input.in", "r", stdin);
	//freopen("output.out", "w", stdout);
#endif
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    //scanf("%d",&t);
    while(~scanf("%d",&n)&&n){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(int i=1;i<n;i++){
            s[i]=a[i+1]-a[i]+89;
        }
        s[n]=0;
        SA(s,n-1,178);
        calheight(s,n-1);
        int l=0;
        int r=n/2;
        int mid;
        ans=0;
        while(l<=r){
            mid=(l+r)>>1;
            if(sloved(mid,n-1)){
                ans=mid;
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        cout<<((ans<=4)?0:ans)<<endl;
    }

#ifdef DEBUG
	printf("Time cost : %lf s\n",(double)clock()/CLOCKS_PER_SEC);
#endif
    //cout << "Hello world!" << endl;
    return 0;
}

C++版本二

#include<stdio.h>
#include<algorithm>
#define ll long long
#define pb push_back
#define INF 0x3f3f3f3f;
#define fi first
#define se second
#define MP make_pair
#define PI pair<int,int>
#define lson l,m,rt<<1,ls,rs
#define rson m+1,r,rt<<1|1,ls,rs
#define test printf("here!!!\n")
using namespace std;
const int mx=2e4+10;
int n,m;
int a[mx];
int s[mx];
int sa[mx],tp[mx],rk[mx],rdx[mx],height[mx];
void get_SA()
{
    for (int i=0;i<=m;++i) rdx[i]=0;
    for (int i=1;i<=n;++i) ++rdx[rk[i]];
    for (int i=1;i<=m;++i) rdx[i]+=rdx[i-1];
    for (int i=n;i>=1;--i) sa[rdx[rk[tp[i]]]--]=tp[i];
}
void SA()
{
    m=200;
    for (int i=1;i<=n;++i) rk[i]=(int)s[i],tp[i]=i;
    get_SA();
    for (int w=1,p=0;p<n;m=p,w<<=1)
    {
        p=0;
        for (int i=n-w+1;i<=n;++i) tp[++p]=i;
        for (int i=1;i<=n;++i)
        {
            if (sa[i]>w) tp[++p]=sa[i]-w;
        }
        get_SA();
        swap(tp,rk);
        rk[sa[1]]=p=1;
        for (int i=2;i<=n;++i)
            rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&((sa[i-1]+w>n&&sa[i]+w>n)||(sa[i-1]+w<=n&&sa[i]+w<=n&&tp[sa[i-1]+w]==tp[sa[i]+w])))?p:++p;//此时的tp为之前的rk,即之前的rk排行,构造新rk
    }
}
void getheight()
{
    int k=0;
    for (int i=1;i<=n;++i)
    {
        if (rk[i]==1) continue;
        if (k) --k;
        int j=sa[rk[i]-1];
        while (i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
        height[rk[i]]=k;
    }
}
int main()
{
    while (~scanf("%d",&n)&&n)
    {
        for (int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]);
        }
        for (int i=1;i<n;++i)
        {
            s[i]=a[i+1]-a[i]+90;
        }
        --n;//care!
        SA();
        getheight();
        int l=0;
        int r=(n+1)/2;//care!
        while (l<=r)
        {
            int flag=0;
            int mid=l+r>>1;//二分答案
            int maxn=sa[1],minn=sa[1];
            for (int i=2;i<=n;++i)
            {//不管height有多大,我只取mid这么长,因为做差了,所以实际上height[i]对应的是height[i]+1这么长的原序列
                if (height[i]>=mid-1)//LCP(i,k)=min(LCP(i,j),LCP(j,k)) (i<=j<=k)
                {//使height始终满足条件,保证上面式子取min始终大于等于mid,因为要保证至少有一处重复
                    maxn=max(maxn,sa[i]);
                    minn=min(minn,sa[i]);
                }//相邻2个肯定是最相似的,如果相邻2个都小于mid,那么与其他之前的串也不会是大于等于mid了
                else maxn=minn=sa[i];//推过来的过程中有不满足的就断了,也是一个起始点
                if (maxn-minn>=mid)//因为是后缀,小的串是大的串的一部分,所以只要两个串的差大于等于mid就能容下小的串,就不会重叠
                {//这里本质上应该是maxn+1-(minn+1)>=mid
                    flag=1;
                    break;
                }
            }
            if (flag)
            {
                l=mid+1;
            }
            else r=mid-1;
        }
        r>=5?printf("%d\n",r):printf("0\n");
    }
}

C++版本三

原博客

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 22222
#define INF (1<<30)
int wa[MAXN],wb[MAXN],wv[MAXN],ws[MAXN];
int cmp(int *r,int a,int b,int l){
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int sa[MAXN],rank[MAXN],height[MAXN];
void SA(int *r,int n,int m){
    int *x=wa,*y=wb;

    for(int i=0; i<m; ++i) ws[i]=0;
    for(int i=0; i<n; ++i) ++ws[x[i]=r[i]];
    for(int i=1; i<m; ++i) ws[i]+=ws[i-1];
    for(int i=n-1; i>=0; --i) sa[--ws[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) ws[i]=0;
        for(int i=0; i<n; ++i) ++ws[wv[i]];
        for(int i=1; i<m; ++i) ws[i]+=ws[i-1];
        for(int i=n-1; i>=0; --i) sa[--ws[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) rank[sa[i]]=i;
    int k=0;
    for(int i=0; i<n-1; height[rank[i++]]=k){
        if(k) --k;
        for(int j=sa[rank[i]-1]; r[i+k]==r[j+k]; ++k);
    }
}

int n,a[MAXN],r[MAXN];
bool isok(int k){
    bool flag=0;
    int mx=-INF,mm=INF;
    for(int i=2; i<=n; ++i){
        if(height[i]>=k){
            mm=min(mm,min(sa[i],sa[i-1]));
            mx=max(mx,max(sa[i],sa[i-1]));
            if(mx-mm>k) return 1;
        }else{
            mx=-INF,mm=INF;
        }
    }
    return 0;
}
int main(){
    while(~scanf("%d",&n) && n){
        for(int i=0; i<n; ++i) scanf("%d",a+i);
        --n;
        for(int i=0; i<n; ++i) r[i]=a[i+1]-a[i]+88;
        r[n]=0;
        SA(r,n+1,176);
        int l=0,r=n>>1;
        while(l<r){
            int mid=l+r+1>>1;
            if(isok(mid)) l=mid;
            else r=mid-1;
        }
        if(l>=4) printf("%d\n",l+1);
        else printf("%d\n",0);
    }
    return 0;
}

C++版本四

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string.h>
#include <queue>                //POJ1743 求不可重叠的最长重复子串(后缀数组)
#define maxn 20005
using namespace std;
int wa[maxn],wb[maxn],wv[maxn],Ws[maxn], Rank[maxn], height[maxn], sa[maxn];
int a[maxn];         
//以下为倍增算法,套模板
/*
height数组是从1到n的 
height数组是排名相邻的两个后缀的最长公共前缀(公共长度)
字符串中的两个子串的公共前缀想要最长,一定是在sa数组相邻的
*/
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(const int *r,int *sa,int n,int m)       //r为字符串(将其都转化为int),n为字符串长度,m为字符的取值范围
{
	int i,j,p,*x=wa,*y=wb,*t; 
	for(i=0;i<m;i++) Ws[i]=0; 
	for(i=0;i<n;i++) Ws[x[i]=r[i]]++; 
	for(i=1;i<m;i++) Ws[i]+=Ws[i-1]; 
	for(i=n-1;i>=0;i--) sa[--Ws[x[i]]]=i; 
	for(j=1,p=1;p<n;j*=2,m=p){ 
		for(p=0,i=n-j;i<n;i++) y[p++]=i; 
		for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; 
		for(i=0;i<n;i++) wv[i]=x[y[i]]; 
		for(i=0;i<m;i++) Ws[i]=0; 
		for(i=0;i<n;i++) Ws[wv[i]]++; 
		for(i=1;i<m;i++) Ws[i]+=Ws[i-1]; 
		for(i=n-1;i>=0;i--) sa[--Ws[wv[i]]]=y[i]; 
		for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++) 
			x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; 
	} 
	return; 
}
//求height数组
void calheight(const int *r,int *sa,int n)
{
	int i,j,k=0;
	for(i=1;i<=n;i++) Rank[sa[i]]=i;
	for(i=0;i<n;height[Rank[i++]]=k)
		for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);
	return;
}
bool check(int k, int n)     //k指代的是长度,二分法来推满足的长度
{
	int t, max1=sa[0], min1=sa[0];
	for(t=1; t<=n; ++t)
	{
		if(height[t]>=k)
		{
			if(sa[t]<min1)
				min1=sa[t];
			if(sa[t]>max1)
				max1=sa[t];
			if(max1-min1>k)
				return true;
		}
		else 
		{
			max1=min1=sa[t];
		}
	}
	return false;
}
 
int main()
{
	int n, t, g, k, ans;
	while(scanf("%d", &n)&&n)
	{
		scanf("%d", &g);
		n--;     //因为后面要创造一个新的数列,相比之下会比之前的少一项
		for(t=0; t<n; ++t)
		{
			scanf("%d", &k);  //作差,创造一个新的数列,因为最长的重复子串里(假设长度为n),就会有n-1个差值一一对应相等(重复的与前面的比较)
			a[t]=k-g+88;     //这样就可以把题目转化成求不可重叠的最长重复子串
			g=k;
		}
		a[n]=0;  //末尾再加一项最小的
		da(a, sa, n+1, 180);
		calheight(a, sa, n);
		int l=0, r=n, mid;
		ans=0;       
		while(l<=r)     //二分法来推满足的长度
		{
			mid=(l+r)>>1;
			if(check(mid, n))   //看看还可不可以深入
			{
				ans=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		if(ans<4)      
		{
			printf("0\n");
		}
		else printf("%d\n", ans+1);     //因为这是一个新的通过作差构造出来的数列,所以个数要+1
	}
	return 0;
}