hiho一下 第123周 后缀数组四·重复旋律4


http://hihocoder.com/contest/hiho123/problem/1

题目1 : 后缀数组四·重复旋律4

时间限制: 5000ms
单点时限: 1000ms
内存限制: 256MB
描述

小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品中的旋律有重复的部分。

我们把一段旋律称为(k,l)-重复的,如果它满足由一个长度为l的字符串重复了k次组成。 如旋律abaabaabaaba是(4,3)重复的,因为它由aba重复4次组成。

小Hi想知道一部作品中k最大的(k,l)-重复旋律。

解题方法提示

输入

一行一个仅包含小写字母的字符串。字符串长度不超过 100000。

输出

一行一个整数,表示答案k。

样例输入
babbabaabaabaabab
样例输出
4



也就是,给一个字符串,

对于一个子串,会有(K,L)由长度为L的部分重复K次。

求所有子串中最大的k。


首先简化版的问题:

给一个字符串,求最大的循环次数。。虽然kmp也是可以求。。不过kmp的求法不便于后面的扩展

可以用后缀数组求,预处理好lcp,预处理好rmq_lcp

对这个字符串,去枚举L,L当然是长度len的约数啦

对于这个L,如果满足LCP(st,st+L)==len-L,那就有一个k=LCP(st,st+L)+1啦 


回到原来的问题,可能这些连续的循环串的起始位置并不在st(起点)或st+L的倍数,这就很麻烦了。。

题解证明了一下

如果最大的k对应的起点X不在st(起点)或st+L的倍数的位置时,我们肯定会找到最接近X的对应的L的倍数的位置P

那么X与P的距离就是  lcp(P,P+L)%L

因此我们就可以得到X的位置

再求一次LCP(X,X+L)/L+1即可



因此总的复杂度就是 第一次枚举可能的长度L(1~n)

对于每个长度L,只需要做n/L次 查询LCP并更新答案 ,O(1)的复杂度

总复杂度O(n/1)+O(n/2)+O(n/3)+O(n/L)+... =O(n*logn);

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 100000+50;
int cmp(int *r,int a,int b,int l)
{
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}
// 用于比较第一关键字与第二关键字,
// 比较特殊的地方是,预处理的时候,r[n]=0(小于前面出现过的字符)
/*
    DA(aa,sa,n+1,200);
    calheight(aa,sa,n);
*/
int wa[N],wb[N],ws[N],wv[N];
int Rank[N];//后缀i在sa[]中的排名
int height[N];//sa[i]与sa[i-1]的LCP
int sa[N];//sa[i]表示排名第i小的后缀的下标
void DA(int *r,int *sa,int n,int m)  //此处N比输入的N要多1,为人工添加的一个字符,用于避免CMP时越界
{
    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; //预处理长度为1
    for(j=1,p=1; p<n; j*=2,m=p) //通过已经求出的长度J的SA,来求2*J的SA
    {
        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; //利用长度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++;  //更新名次数组x[],注意判定相同的
    }
}

void calheight(int *r,int *sa,int n)  // 此处N为实际长度
{
    int i,j,k=0;        // height[]的合法范围为 1-N, 其中0是结尾加入的字符
    for(i=1; i<=n; i++) Rank[sa[i]]=i; // 根据SA求Rank
    for(i=0; i<n; height[Rank[i++]] = k ) // 定义:h[i] = height[ Rank[i] ]
        for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++); //根据 h[i] >= h[i-1]-1 来优化计算height过程
}
int n;
char ss[N];
int aa[N];
const int maxn=100000+50;
int mn[100000][20];
int log22[100000+50];
void pre()
{
    for (int i=1; i<=n; i++)
        log22[i]=log2(i);
}
void rmq_init(int n,int *h)
{
    for (int j=1; j<=n; j++) mn[j][0]=h[j];
    int m=log22[n];
    for (int i=1; i<=m; i++)
        for (int j=n; j>0; j--)
        {
            mn[j][i]=mn[j][i-1];
            if ( j+(1<<(i-1)) <=n ) mn[j][i]=min(mn[j][i], mn[j+(1<<(i-1)) ] [i-1]);
        }
}
int lcp_min(int l,int r) //求lcp(l,r)
{
    if (l>r)swap(l,r);  //先交换
    l++;                //根据height定义,l++
    int m=log22[r-l+1];
    return min(mn[l][m],mn[r-(1<<m)+1][m]);
}

int solve()
{
    int ans=1;
    for (int  L=1; L<=n; L++)
    {
        for (int j=0; j<n; j+=L)
        {
            int lcp_len=lcp_min( Rank[j],Rank[j+L]);
            ans=max(ans,lcp_len/L+1);
            int last_possible_pos=j-(L-lcp_len%L);
            if (last_possible_pos>=0)
                ans=max(ans, 1+lcp_min( Rank[last_possible_pos] ,Rank[last_possible_pos+L]   )/L  ) ;
        }
    }
    return ans;
}

int main ()
{
 //    printf("%d ",'z'-'a');
    scanf("%s",&ss);
    n=strlen(ss);
    for (int i=0; i<n; i++) 
        aa[i]=ss[i]-'a'+1; 
    aa[n]=0;
    DA(aa,sa,n+1,30);
    calheight(aa,sa,n);
    pre();
    rmq_init(n,height);
    //---------------------------------------------pre
    int ans= solve();
    printf("%d\n",ans);
    return 0;
}




 







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值