[POJ1743]Musical Theme(后缀数组+二分 / 后缀自动机)

传送门


复习了后缀数组。
首先因为转调我们需要对字符串作差。

考虑二分+后缀数组的做法。
二分答案k,将连续的height[i]>=x的段分组,如果一组内sa的最大值与最小值的差>=k,则k可行。

后缀自动机也是可以的。
因为right集合即为子串在母串中出现位置的右端点的集合,求出right集合就可以确定串的出现位置,那么求出每个点right集合中的最大值和最小值,如果他们的差值不小于该点的max,那么就说明这个位置代表的子串重复出现且没有重合,用max更新答案即可(因为max是这个节点所包含的子串中最长的那个,如果这个节点所包含的子串的最早出现位置和最晚出现位置的差比max多的话,说明中间塞得下,所以最少有两个以上的不重合子串)。

个人认为后缀自动机比较好想。


后缀数组:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int a[21000];
int rsort[21000],Rank[21000],sa[21000],y[21000],wr[21000];
bool cmp(int k1,int k2,int ln) 
{
    return wr[k1]==wr[k2]&&wr[k1+ln]==wr[k2+ln];
}
void get_sa(int n,int m)
{
    memset(rsort,0,sizeof(rsort));
    for(int i=1;i<=n;i++) Rank[i]=a[i];
    for(int i=1;i<=n;i++) rsort[Rank[i]]++;
    for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
    for(int i=n;i>=1;i--) sa[rsort[Rank[i]]--]=i;
    int p=0,k=0,ln=1;
    while (p<n)
    {
        k=0;
        for(int i=n-ln+1;i<=n;i++) y[++k]=i;
        for(int i=1;i<=n;i++) if (sa[i]>ln) y[++k]=sa[i]-ln;

        memset(rsort,0,sizeof(rsort));
        for(int i=1;i<=n;i++) rsort[Rank[y[i]]]++;
        for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
        for(int i=n;i>=1;i--) sa[rsort[Rank[y[i]]]--]=y[i];

        for(int i=1;i<=n;i++) wr[i]=Rank[i];
        Rank[sa[1]]=1;p=1;
        for(int i=2;i<=n;i++) 
        {
            if (cmp(sa[i],sa[i-1],ln)==0) p++;
            Rank[sa[i]]=p;
        }
        ln*=2;m=p;
    }
}
int height[21000];
void get_he(int n)
{
    int k=0;
    for (int i=1;i<=n;i++)//位置 
    {
        int j=sa[Rank[i]-1];
        if (k) k--;
        while (a[i+k]==a[j+k]) k++;
        height[Rank[i]]=k;
    }
}
bool check(int k,int n)
{
    for(int i=2;i<=n;i++)
    {
        if(height[i]<k) continue;
        for(int j=i-1;j>=1;j--)
        {
            if(abs(sa[i]-sa[j])>=k) return true;
            if(height[j]<k) break;
        }
    }
    return false;
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF && n)
    {
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        int mx=-1e8;
        for(int i=1;i<n;i++)
        {
            a[i]=a[i+1]-a[i]+88;
            mx=max(mx,a[i]);
        }
        get_sa(n,mx);
        get_he(n);
        int ans=1,l=1,r=n;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(check(mid,n)==1)
            {
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        if (ans+1>=5) printf("%d\n",ans+1);
        else printf("0\n");
    }
    return 0;
}

后缀自动机:
试了试数组版的SAM,感觉没有结构体版的写的爽啊。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
const int N=40003;
using namespace std;
int n,m,cnt,last,root,a[N],Rs[N],sa[N];
int pre[N],mxx[N],ch[N][180],mx[N],mn[N];
void extend(int k)
{
    int x=a[k];
    int p=last; int np=++cnt; last=np; 
    mxx[np]=mxx[p]+1; 
    while(p && !ch[p][x] ) ch[p][x]=np,p=pre[p];
    if(!p) pre[np]=root;
    else
    {
        int q=ch[p][x];
        if (mxx[q]==mxx[p]+1) pre[np]=q;
        else
        {
            int nq=++cnt; 
            mxx[nq]=mxx[p]+1; 
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            pre[nq]=pre[q];
            pre[q]=pre[np]=nq;
            while(p && ch[p][x]==q) ch[p][x]=nq,p=pre[p];
        }
    }
}
int solve()
{
    memset(Rs,0,sizeof(Rs));
    for(int i=1;i<=cnt;i++) Rs[mxx[i]]++;
    for(int i=1;i<=n;i++) Rs[i]+=Rs[i-1];
    for(int i=1;i<=cnt;i++) sa[Rs[mxx[i]]--]=i;
    int p=1;
    memset(mx,0,sizeof(mx));
    memset(mn,127,sizeof(mn));
    for(int i=1;i<=n;i++)
    {
        p=ch[p][a[i]]; 
        mx[p]=mn[p]=i;
    }
    for(int i=cnt;i;i--)
    {
        int t=sa[i];
        mx[pre[t]]=max(mx[pre[t]],mx[t]);
        mn[pre[t]]=min(mn[pre[t]],mn[t]);
    }
    int ans=0;
    for(int i=1;i<=cnt;i++) if(mx[i]-mn[i]>=mxx[i]) ans=max(ans,mxx[i]);

    ans++;
    if (ans<5) ans=0;
    return ans;
}
int main()
{
//  freopen("a1.in","r",stdin);
//  freopen("a1.out","w",stdout);
    while(1)
    {
        scanf("%d",&n);
        if(!n) break; 
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<n;i++) a[i]=a[i+1]-a[i]+88; 
        n--;
        memset(mxx,0,sizeof(mxx));
        memset(pre,0,sizeof(pre));
        memset(ch,0,sizeof(ch)); 
        cnt=0; last=root=++cnt;
        for (int i=1;i<=n;i++) extend(i); 
        printf("%d\n",solve());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值