POJ 1743 Musical Theme(后缀数组 求最长不重复子串)


对于后缀数组强烈建议看国家集训队 04年和09年的两篇关于后缀数组的论文


后缀数组非常强大,但是这么强大的数据结构理解起来不是那么容易的,里面涉及到好多知识点,要想彻底理解后缀数组,表示需要一点功底才行

对于后缀数组具体请看国家集训对09年和04年的两篇大牛关于后缀数组的论文,两篇结合起来看效果会更好,可能看第一第二遍的时候不是怎么理解

这个是肯定的,相信那两个大牛写这两篇论文的时候花的时间也不止几天吧,人家花几天写的论文给你一下子就看懂了,你当人家写的是小说啊,一般

像这种复杂的数据结构要多看几遍,甚至当你认为你已经看懂之后不妨再多看几遍,这样会有更深的理解!

后缀数组主要是两个 一个是后缀数组,一个是名次数组,这两个数组相互搭配完成后缀数组的强大功能

后缀数组sa保存的是拍名,也就是下标表示排名,内容表示排这个名的是第几个后缀

rank数组正好相反,是第下标个后缀的排名

然后用倍增算法实现对后缀数组的构造,我开始看的时候没看懂,后来稍微懂了之后发现这个算法用的也不是什么高深的算法

对于这个算法的理解还是要在对字符串比较的过程有一定的理解,这个题目关键就是按照字符出现的位置对字符赋予了类似整数

比较的一个权值,在前面的字符大小最总要,也就是个十百千位那样,先求一个长度的排名,然后根据一个的求两个的,一次往后

最后完成整个后缀数组的构造

构造完成之后这个后缀数组好像还不能干什么事情,接下来又引进了height数组(具体定义请看论文),这个height数组保存的是

排名相邻的两个后缀的最长前缀,现在我们关键任务是怎样在最短时间内构造出这个height数组,论文上给了O(n)的复杂度,

这个求法类似于动态规划的思想,利用前面的结果,在其基础之上跟新现在的结果

完了之后就是这个题目的内容了

这个题目的思想是二分枚举,和判断重复,然后按照height的值进行分组,分组完成之后判断组内最大位置和最小位置的差值是否

满足题目条件,判断是否满足题目要求,具体为什么分组和最大位置最小位置差就能确定,看看下面代码注释

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <math.h>
using namespace std;
#define maxn 21000
#define ws ws1
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];}
void da(int *r,int *sa,int n,int 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;
}
int rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n)
{
int i,j,k=0;
for(i=1;i<=n;i++) rank[sa[i]]=i;//在计算height的时候顺便就把rank计算出来了,反正也要用
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;
}
int rec[maxn],rec1[maxn],sa[maxn];
int n;
bool check(int m)
{
    int mmax=0,mmin=n;
   /*这里说的就是论文上对height数组进行分组
    *这个分组是按照顺序来分组的,也就是把整个height数组按照比m大和没有m大分成一段一段,如果在
    *在某一段内存在答案就说明成立
    *还是按照自己的理解解释一下这里为什么按照这样分组就可以,为什么是这个分组区间的最左边和最右边的不相交就可以
    *想一想如果一个区间的height值都比m大,那么这么多组后缀的前m个一定是相同的,因为是前一组前m个等于后一组m个,
    *而后一的前m个又等于后后一组的,这样推出来,所以这样分组是正确的
    */
    for(int i=1;i<=n;i++)
    {
        if(height[i]<m)
        {
            mmax=sa[i];
            mmin=sa[i];
        }
        else
        {
            mmax=max(mmax,max(sa[i],sa[i-1]));
            mmin=min(mmin,min(sa[i],sa[i-1]));
            if(mmax-mmin>m) return true;
        }
    }
    return false;
}
int main()
{
    int i,j,k;
    int left,right;
    int mid;
    int answer;
    while(scanf("%d",&n),n)
    {
     for(i=0;i<n;i++)
     scanf("%d",&rec1[i]);
     for(i=0;i<n-1;i++)
     rec[i]=rec1[i+1]-rec1[i]+100;
     n--;
     rec[n]=0;
     da(rec,sa,n+1,200);//这里是n+1 因为看这个函数里面是 < n 的
     calheight(rec,sa,n);//注意这里面是 n 了因为看函数里面是 <=n 的,所以这里要注意
     right=-1;
     for(i=0;i < n ;i++)
     if(right < height[i])
     right=height[i];
     //for(i=0;i<=n;i++)
    // printf("%d ",height[i]);
    // printf("\n");
     if(!check(4))
     {
         printf("0\n");
         continue;
     }
     left=4;
     answer=4;
     while(left <= right)
     {
         mid=(left+right)>>1;
         if(check(mid))
         {
             answer=mid;
             left=mid+1;
         }
         else
         {
             right=mid-1;
         }
     }
     printf("%d\n",answer+1);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值