[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());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章