zoukankan      html  css  js  c++  java
  • 后缀数组suffix array

    自用模板:

    sa:字典序中排第i位的起使位置在str中第sa[i],sa[1,n]
    rk:str第i个位置的后缀在字典序中排第几,rk[1,n]
    height:字典序排i和i-1的后缀的最长公共前缀,height[2,n]

    #include<bits/stdc++.h>
    #define maxn 1000050
    using namespace std;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m;
    
    inline void get_SA() 
    {
        for(int i=1;i<=n;++i) ++c[x[i]=s[i]];
        //c数组是桶
        //x[i]是第i个元素的第一关键字
        for(int i=2;i<=m;++i) c[i]+=c[i-1];
        //做c的前缀和,我们就可以得出每个关键字最多是在第几名
        for(int i=n;i>=1;--i) sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1) 
        {
            int num=0;
            for(int i=n-k+1;i<=n;++i) y[++num]=i;
            //y[i]表示第二关键字排名为i的数,第一关键字的位置
            //第n-k+1到第n位是没有第二关键字的 所以排名在最前面
            for(int i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k;
            //排名为i的数 在数组中是否在第k位以后
            //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
            //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
            for(int i=1;i<=m;++i) c[i]=0;
            //初始化c桶
            for(int i=1;i<=n;++i) ++c[x[i]];
            //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了
            for(int i=2;i<=m;++i) c[i]+=c[i-1]; //第一关键字排名为1~i的数有多少个
            for(int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
            //因为y的顺序是按照第二关键字的顺序来排的
            //第二关键字靠后的,在同一个第一关键字桶中排名越靠后
            //基数排序
            swap(x,y);
            //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;++i)
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num:++num;
            //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
            if(num==n) break;
            m=num;
            //这里就不用那个m了,因为都有新的编号了
        }
    }
    inline void get_height() 
    {
        int k=0;
        for(int i=1;i<=n;++i) rk[sa[i]]=i;
        for(int i=1;i<=n;++i) 
        {
            if(rk[i]==1) continue;//第一名height为0
            if(k) --k;//h[i]>=h[i-1]-1;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
            height[rk[i]]=k;//h[i]=height[rk[i]];
        }
    }
    int main() 
    {
    
        return 0;        
    }
    View Code

    这板子我能打错无数遍o(╥﹏╥)o

    单字符串:

    可重叠最长重复子串:height[i]的max

    不可重叠最长重复子串(相似子串)

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e4+10;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m=150;
    
    inline void get_SA()
    {
        for(int i=1;i<=n;i++)++c[x[i]=s[i]];
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)++c[x[i]];
            for(int i=2;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n)break;
            m=num; 
        }
    }
    
    inline void get_height()
    {
        int k=0;
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        for(int i=1;i<=n;i++)
        {
            if(rk[i]==1)continue;
            if(k)--k;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    } 
    
    bool judge(int num)
    {
        int l=sa[1],r=sa[1];
        for(int i=2;i<=n;i++)
        {
            if(height[i]>=num)
            {
                l=min(l,sa[i]);
                r=max(r,sa[i]);
            }
            else
            {
                l=r=sa[i];
            }
            if(r-l>num)return true;
        }
        return false;
    }
    
    int main()
    {
        scanf("%d",&n);
        int a[maxn]={0};
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            if(i!=1)s[i-1]=a[i]-a[i-1]+48;
        }
        n--;
        get_SA();
        get_height();
        
        int low=1,high=n,mid,ans=0;
        while(low<=high)
        {
            mid=(low+high)>>1;
            if(judge(mid))
            {
                ans=mid;
                low=mid+1;
            }
            else
            {
                high=mid-1;
            }
        }
        printf("%d
    ",ans+1);
        return 0;
    }
    View Code

    可重叠至少k次重复子串

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e4+10;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m=150,k;
    
    inline void get_SA()
    {
        for(int i=1;i<=n;i++)++c[x[i]=s[i]];
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)++c[x[i]];
            for(int i=2;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n)break;
            m=num; 
        }
    }
    
    inline void get_height()
    {
        int k=0;
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        for(int i=1;i<=n;i++)
        {
            if(rk[i]==1)continue;
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    
    bool judge(int num)
    {
        int cnt=1;
        for(int i=2;i<=n;i++)
        {
            if(height[i]>=num)
            {
                cnt++;
            }
            else
            {
                cnt=1;
            }
            if(cnt>=k)return true;
        }
        return false;
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
        {
            int num;
            scanf("%d",&num);
            s[i]=num+48;
        }
        get_SA();
        get_height();
        int low=1,high=n,mid,ans=0;
        while(low<=high)
        {
            mid=(low+high)>>1;
            if(judge(mid))
            {
                ans=mid;
                low=mid+1;
            }
            else
            {
                high=mid-1;
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    不同字串个数:对n-sa[i]+1-height[i]求和

    求第k大子串左右端点,hdu5008:

    预处理每个后缀里有多少子串,找出第k大的子串第一次出现的后缀,之后二分+rmq找左右端点

    https://blog.csdn.net/weixin_43093481/article/details/82875115

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn=1e5+5;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m=131;
    ll sum[maxn],dp1[maxn][30],dp2[maxn][30];
    
    inline void get_SA()
    {
        for(int i=1;i<=n;i++)++c[x[i]=s[i]];
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)++c[x[i]];
            for(int i=2;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n)break;
            m=num;
        }
    }
    
    inline void get_height()
    {
        int k=0;
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        for(int i=1;i<=n;i++)
        {
            if(rk[i]==1)continue;
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    
    void cal()
    {
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            sum[i]=sum[i-1]+n-sa[i]+1-height[i];
        }
        return;
    }
    
    void rmq_init()
    {
        for(int i=1;i<=n;i++)
        {
            dp1[i][0]=height[i];
            dp2[i][0]=sa[i];
        }
        for(int j=1;(1<<j)<=n;j++)
        {
            for(int i=1;i+(1<<j)-1<=n;i++)
            {
                dp1[i][j]=min(dp1[i][j-1],dp1[i+(1<<j-1)][j-1]);
                dp2[i][j]=min(dp2[i][j-1],dp2[i+(1<<j-1)][j-1]);
            }
        }
    }
    ll rmq1(int l,int r)
    {
        int k=log2(r-l+1);
        return min(dp1[l][k],dp1[r-(1<<k)+1][k]);
    }
    
    ll rmq2(int l,int r)
    {
        int k=log2(r-l+1);
        return min(dp2[l][k],dp2[r-(1<<k)+1][k]);
    }
    
    int main()
    {
        //freopen("1.txt","r",stdin);
        //freopen("2.txt","w",stdout); 
        scanf("%s",s+1);
        n=strlen(s+1);
        get_SA();
        get_height();
        cal();
        rmq_init();
        
        int q;
        ll lp=0,rp=0,k;
        scanf("%d",&q);
        while(q--)
        {
            scanf("%lld",&k);
            k^=lp,k^=rp,k++;
            if(k>sum[n])
            {
                lp=rp=0;
            }
            else
            {
                int pos=lower_bound(sum+1,sum+1+n,k)-sum;
                k-=sum[pos-1];
                ll len=height[pos]+k;
                int low=pos+1,high=n,mid,ans=pos;
                while(low<=high)
                {
                    mid=(low+high)>>1;
                    if(rmq1(pos+1,mid)>=len)
                    {
                        ans=mid;
                        low=mid+1;
                    }
                    else
                    {
                        high=mid-1; 
                    }
                }
                ll tmp=rmq2(pos,ans);
                lp=tmp;
                rp=tmp+len-1;
            }
            printf("%lld %lld
    ",lp,rp);
        }
        return 0; 
    } 
    View Code

    不可重叠重复子串个数 

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e3+10;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m=131;
    
    inline void get_SA()
    {
        for(int i=1;i<=n;i++)++c[x[i]=s[i]];
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)++c[x[i]];
            for(int i=2;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n)break;
            m=num; 
        }
    }
    inline void get_height()
    {
        int k=0;
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        for(int i=1;i<=n;i++)
        {
            if(rk[i]==1)continue;
            if(k)--k;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    
    int judge(int num)
    {
        int l=sa[1],r=sa[1],cnt=0;
        for(int i=2;i<=n;i++)
        {
            if(height[i]>=num)
            {
                l=min(l,sa[i]);
                r=max(r,sa[i]);
            }
            else
            {
                if(r-l>=num)cnt++;
                l=r=sa[i];
            }
        }
        if(r-l>=num)cnt++;
        return cnt;
    }
    
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        get_SA();
        get_height();
    
        int ans=0;
        for(int i=1;i<=n/2;i++)
        {
            ans+=judge(i);
        }
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    两个字符串:

    最长公共子串:两串之间用‘$’连接

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e5+10;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m=131;
    
    inline void get_SA()
    {
        for(int i=1;i<=n;i++)++c[x[i]=s[i]];
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)++c[x[i]];
            for(int i=2;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n)break;
            m=num;
        }
    }
    
    inline void get_height()
    {
        int k=0;
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        for(int i=1;i<=n;i++)
        {
            if(rk[i]==1)continue;
            if(k)--k;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    
    int main()
    {
        int n1,n2;
        scanf("%s",s+1);
        n1=strlen(s+1);
        s[n1+1]='$';
        scanf("%s",s+n1+2);
        n2=strlen(s+1)-n1-1;
        n=n1+n2+1;
        get_SA();
        get_height();
        
        int ans=0;
        for(int i=2;i<=n;i++)
        {
            if(height[i]>ans)
            {
                if(sa[i]<n1+1&&sa[i-1]>n1+1||sa[i]>n1+1&&sa[i-1]<n1+1)
                ans=height[i];
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    长度不小于K的公共子串个数

    多个字符串:

    其他串没有的子串

    多串的最长公共子串

    不小于个串的最长子串

    每个串中至少出现两次且不重叠的子串个数

    出现或反转出现在每个字符串的最长子串

    hdu5343 sam后缀自动机+记忆化搜索

    这个专题好难,不知道啥时候补...

  • 相关阅读:
    用DecimalFormat格式化十进制数字的实际应用
    ETL测试小结
    浅谈DB2的四个隔离级别
    POI处理Excel中各种日期格式问题
    如何将Object类型转换成String类型
    ArrayList和LinkedList
    DB2 编目并访问远程数据库
    EXCEL解析之终极方法WorkbookFactory
    DB2中coalesce函数的应用
    【Linux学习】Linux用户管理2—用户配置文件
  • 原文地址:https://www.cnblogs.com/myrtle/p/11413391.html
Copyright © 2011-2022 走看看