zoukankan      html  css  js  c++  java
  • poj1743 Musical Theme(后缀数组|后缀自动机)

     
    【题目链接】
     
      
     
     
    【题意】
     
     
      求不可重叠最长重复子串。
     

    2015-11-27

    【思路】

      

      1)      据题意处理字符串

      2)      后缀数组。二分长度k,问题成为了判定是否存在两个及以上长度不小于k且互不重叠的子串。根据height数组划分后缀,满足两个条件:一是一组内height值不小于k(保证组内任两个长度不小于k即存在长度不小于k的子串),二是组内后缀sa值的最大最小值之差大于等于k(保证两个子串不重叠)。

      需要注意n==1时需要特判。

      1/为什么可以划分height数组呢?首先height[i]代表lcp(suffix(sa[i]),suffix(sa[i-1])),所以height所对应的后缀是有序的,如果划分出现height<k的话以后的后缀与改组内的lcp一定不大于k-1,所以不会出现后面的再划分到改组的情况。

    【代码】

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 #define FOR(a,b,c) for(int a=(b);a<=(c);a++)
     5 using namespace std;
     6 
     7 const int maxn = 40000+10;
     8 
     9 int s[maxn];
    10 int sa[maxn],t[maxn],t2[maxn],c[maxn],n;
    11 
    12 
    13 void build_sa(int m) {
    14     int i,*x=t,*y=t2;
    15     for(int i=0;i<m;i++) c[i]=0;
    16     for(int i=0;i<n;i++) c[x[i]=s[i]]++;
    17     for(int i=1;i<m;i++) c[i]+=c[i-1];
    18     for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    19     
    20     for(int k=1;k<=n;k<<=1) {
    21         int p=0;
    22         for(int i=n-k;i<n;i++) y[p++]=i;
    23         for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
    24         
    25         for(int i=0;i<m;i++) c[i]=0;
    26         for(int i=0;i<n;i++) c[x[y[i]]]++;
    27         for(int i=0;i<m;i++) c[i]+=c[i-1];
    28         for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
    29         
    30         swap(x,y);
    31         p=1;  x[sa[0]]=0;
    32         for(int i=1;i<n;i++)
    33              x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
    34         if(p>=n)  break;
    35         m=p;
    36     }
    37 }
    38 
    39 int rank[maxn],height[maxn];
    40 void getHeight() {
    41     int i,j,k=0;
    42     for(int i=0;i<n;i++) rank[sa[i]]=i;
    43     for(int i=0;i<n;i++) {
    44         if(k) k--;
    45         int j=sa[rank[i]-1];
    46         while(s[i+k]==s[j+k]) k++;
    47         height[rank[i]]=k;
    48     }
    49 }
    50 
    51 bool can(int k) {
    52     int min=sa[0],max=sa[0];
    53     for(int i=1;i<n;i++) {
    54         if(height[i]<k) min=max=sa[i]; 
    55         if(sa[i]<min) min=sa[i];
    56         if(sa[i]>max) max=sa[i];
    57         if(max-min>=k) return true;
    58     }
    59     return false;
    60 }
    61 
    62 int main() {
    63     while(scanf("%d",&n)==1 && n) 
    64     {
    65         for(int i=0;i<n;i++)scanf("%d",&s[i]);
    66         for(int i=n-1;i>0;i--)s[i]=s[i]-s[i-1]+100;
    67         n--;
    68         for(int i=0;i<n;i++)s[i]=s[i+1];
    69         s[n]=0;        
    70         build_sa(200);
    71         getHeight();
    72         int L=0,R=n/2;
    73         while(L<R) {
    74             int M=L+(R-L+1)/2;
    75             if(can(M)) L=M;  else R=M-1;
    76         }
    77         L++;                      
    78         if(L<=4) printf("0
    ");
    79         else printf("%d
    ",L);
    80     }
    81     return 0;
    82 }
    View Code

     2016-2-19

    【思路】

       

      SAM+DP

      处理出right集的最大值mx和最小值mn,即该状态对应所有字符串的结束位置的最大与最小,递推式为:

        mx[p]=max{ l[p], mx[np],np->fa=p }

        mn[p]=min{ l[p], mn[np],np->fa=p }

      则状态i对应字符串中的最长重复子串的长度为min{l[i],mx[i]-mn[i]},可以拿个栗子自己看一下,这样保证了不重叠。然后取所有状态的最大值即可。

    【代码】

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 using namespace std;
     5 
     6 const int N = 4*1e4+10;
     7 const int sigma = 180;
     8 
     9 int s[N/2];
    10 int root,last,sz,ch[N][sigma],fa[N],l[N],mn[N],mx[N];
    11 int b[N],cnt[N],n;
    12 
    13 void init() {
    14     sz=0; root=last=++sz;
    15     memset(fa,0,sizeof(fa));
    16     memset(mx,0,sizeof(mx));
    17     memset(mn,127,sizeof(mn));
    18     memset(cnt,0,sizeof(cnt));
    19     memset(ch,0,sizeof(ch));
    20 }
    21 void add(int x) {
    22     int c=s[x];
    23     int p=last,np=++sz; last=np;
    24     mn[np]=mx[np]=l[np]=x;
    25     for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
    26     if(!p) fa[np]=root;
    27     else {
    28         int q=ch[p][c];
    29         if(l[p]+1==l[q]) fa[np]=q;
    30         else {
    31             int nq=++sz; l[nq]=l[p]+1;
    32             memcpy(ch[nq],ch[q],sizeof(ch[q]));
    33             fa[nq]=fa[q];
    34             fa[np]=fa[q]=nq;
    35             for(;p&&q==ch[p][c];p=fa[p]) ch[p][c]=nq;
    36         }
    37     }
    38 }
    39 void solve() {
    40     for(int i=1;i<=sz;i++) cnt[l[i]]++;
    41     for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
    42     for(int i=1;i<=sz;i++) b[cnt[l[i]]--]=i;
    43     int ans=0;
    44     for(int i=sz;i;i--) {
    45         int p=b[i];
    46         if(fa[p]) {
    47             if(mn[fa[p]]>mn[p]) mn[fa[p]]=mn[p];
    48             if(mx[fa[p]]<mx[p]) mx[fa[p]]=mx[p];
    49         }
    50     }
    51     for(int i=1;i<=sz;i++)
    52         ans=max(ans,min(l[i],mx[i]-mn[i]));
    53     if(ans<4) puts("0");
    54     else printf("%d
    ",ans+1);
    55 }
    56 void read(int& x) {
    57     char c=getchar(); int f=1; x=0;
    58     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    59     while(isdigit(c)) x=x*10+c-'0',c=getchar();
    60     x*=f;
    61 }
    62 
    63 int main() {
    64     while(read(n),n) {
    65         init();
    66         for(int i=1;i<=n;i++) read(s[i]); n--;
    67         for(int i=1;i<=n;i++) s[i]=s[i+1]-s[i]+88,add(i);
    68         solve();
    69     }
    70     return 0;
    71 }
    View Code
  • 相关阅读:
    台州 OJ 3847 Mowing the Lawn 线性DP 单调队列
    洛谷 OJ P1417 烹调方案 01背包
    快速幂取模
    台州 OJ 2649 More is better 并查集
    UVa 1640
    UVa 11971
    UVa 10900
    UVa 11346
    UVa 10288
    UVa 1639
  • 原文地址:https://www.cnblogs.com/lidaxin/p/5001802.html
Copyright © 2011-2022 走看看