• ACM-ICPC(10/21)


    写一发后缀数组套路题,看起来简单,写起来要人命哦~~~

    总共13题。

    分两天debug吧,有点累了~~~

    suffix(后缀数组的应用)

    • sa[i] :排名第 i 的后缀在哪(i 从 1 开始)

    • rank[i]:后缀 i 排第几 (i 从 0 开始)

    • height[i]:排名为 i 和 i-1 的两个后缀的最长公共前缀(LCP)长度 (i 从 2 开始)

    模板:加上RMQ操作

    #include <stdio.h>
    #include <string.h>
    #include <math.h>
    #include <algorithm>
    ​
    ​
    using namespace std;
    ​
    const int maxn = 2000000+5;
    ​
    int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
    int sa[maxn];
    int r[maxn];
    ​
    int cmp(int *r,int a,int b,int l)
    {
        return r[a]==r[b]&&r[a+l]==r[b+l];
    }
    void da(int *r,int *sa,int n,int m)
    {
        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;
        for(j=1,p=1; p<n; j*=2,m=p)
        {
            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;
            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++;
        }
        return;
    }
    int ranks[maxn],height[maxn];
    void calheight(int *r,int *sa,int n)
    {
        int i,j,k=0;
        for(i=1; i<=n; i++) ranks[sa[i]]=i;
        for(i=0; i<n; height[ranks[i++]]=k)
            for(k?k--:0,j=sa[ranks[i]-1]; r[i+k]==r[j+k]; k++);
        return;
    }
    ​
    ​
    char str[maxn];
    ​
    int f[maxn][20];
    void init(int len) {
        for(int i = 1; i <= len; i++) f[i][0] = height[i];
        for(int s = 1; (1<<s)<=len; s++) {
            int tmp = (1<<s);
            for(int i = 1; i+tmp-1<=len; i++) {
                f[i][s] = min(f[i][s-1],f[i+tmp/2][s-1]);
            }
        }
    }
    ​
    int cal(int l,int r) {
        int len = log2(r-l+1);
        int ans = min(f[l][len],f[r-(1<<len)+1][len]);
        return ans;
    }
    ​
    ​
    ​
    int main()
    {
        freopen("in.txt","r",stdin);
    ​
        scanf("%s",str);
        int n = strlen(str);
    ​
        for(int i = 0; i < n; i++)
            r[i] = str[i] - 'a' + 1;
        da(r,sa,n+1,300);
        calheight(r,sa,n);
    ​
        for(int i = 1; i <= n; i++) {
            printf("%d ",sa[i]);
        }
        puts("");
    ​
        for(int i = 0; i < n; i++) {
            printf("%d ",ranks[i]);
        }
        puts("");
    ​
        for(int i = 2; i <= n; i++) {
            printf("%d ",height[i]);
        }
        puts("");
    ​
        init(n);
    ​
        //height 上的 RMQ(); 从 2开始;
        printf("%d
    ",cal(2,3));
        return 0;
    }
    View Code
    
    

    例题一:最长公共前缀

    给定一个字符串,询问某两个后缀的最长公共前缀。

    分析:根据图,某两个后缀的LCP,是一个区间的RMQ;(也是定理)

    
    
    #include <stdio.h>
    #include <string.h>
    #include <math.h>
    #include <algorithm>
    ​
    ​
    using namespace std;
    ​
    const int maxn = 2000000+5;
    ​
    int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
    int sa[maxn];
    int r[maxn];
    ​
    int cmp(int *r,int a,int b,int l)
    {
        return r[a]==r[b]&&r[a+l]==r[b+l];
    }
    void da(int *r,int *sa,int n,int m)
    {
        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;
        for(j=1,p=1; p<n; j*=2,m=p)
        {
            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;
            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++;
        }
        return;
    }
    int ranks[maxn],height[maxn];
    void calheight(int *r,int *sa,int n)
    {
        int i,j,k=0;
        for(i=1; i<=n; i++) ranks[sa[i]]=i;
        for(i=0; i<n; height[ranks[i++]]=k)
            for(k?k--:0,j=sa[ranks[i]-1]; r[i+k]==r[j+k]; k++);
        return;
    }
    ​
    ​
    char str[maxn];
    ​
    int f[maxn][20];
    void init(int len) {
        for(int i = 1; i <= len; i++) f[i][0] = height[i];
        for(int s = 1; (1<<s)<=len; s++) {
            int tmp = (1<<s);
            for(int i = 1; i+tmp-1<=len; i++) {
                f[i][s] = min(f[i][s-1],f[i+tmp/2][s-1]);
            }
        }
    }
    ​
    int cal(int l,int r) {
        int len = log2(r-l+1);
        int ans = min(f[l][len],f[r-(1<<len)+1][len]);
        return ans;
    }
    ​
    ​
    ​
    int main()
    {
        freopen("in.txt","r",stdin);
    ​
        scanf("%s",str);
        int n = strlen(str);
    ​
        for(int i = 0; i < n; i++)
            r[i] = str[i] - 'a' + 1;
        da(r,sa,n+1,300);
        calheight(r,sa,n);
    ​
        for(int i = 1; i <= n; i++) {
            printf("%d ",sa[i]);
        }
        puts("");
    ​
        for(int i = 0; i < n; i++) {
            printf("%d ",ranks[i]);
        }
        puts("");
    ​
        for(int i = 2; i <= n; i++) {
            printf("%d ",height[i]);
        }
        puts("");
    ​
        init(n);
    ​
        int l,r;    // 询问后缀L,R的最长公共前缀,从0开始
    ​
        scanf("%d%d",&l,&r);
        l = ranks[l];
        r = ranks[r];
    ​
        if(l>r) swap(l,r);
    ​
        l++;
        printf("%d
    ",cal(l,r));
    ​
        return 0;
    }
    View Code

    例题二:可以重叠的最长重复子串

    给定一个字符串,求最长的重复子串(出现了多次>1),这两个子串可以重叠。

    分析:子串可以写成一个后缀,题目可以转换为求:最长的两个后缀的最长公共前缀。任意两个后缀的LCP,都是height数组里面某一段的最小值,那么这个值一定不大于height 的最大值。

    int ans = -1;
    for(int i = 2; i <= n; i++)
        ans = max(ans,height[i]);
    View Code

    例题三:不可重叠最长重复子串(pku1743)

    给定一个字符串,求最长重复子串,这两个子串不能重叠。(当然原题题意没这么简单咯~~~原题是音乐家演奏,求两组最长的旋律,要求这两组旋律一下(只可以相差一个常数))

    我这里写字符串的,其实转换过来很简单的(考虑其差值,就转换过来了)

    分析:遇到不重叠——二分分组。

    先二分答案,把问题变成一个判定性题目,将排序排序后的后缀按照mid 分成若干组,每组的后缀自检的height>=mid,每个区间内找两个后缀满足不重叠即可。

    原理:可以看出,游戏王成为最长公共前缀>=mid 的两个后缀,一定在一个分组里面。这个分组里面查不相交的两个位置。

    
    
    int main()
    {
        freopen("in.txt","r",stdin);
    ​
        scanf("%s",str);
        int n = strlen(str);
    ​
        for(int i = 0; i < n; i++) r[i] = str[i] - 'a' + 1;
    ​
        da(r,sa,n+1,300);
        calheight(r,sa,n);
    ​
        int l = 1,r= n;
        int ans = 0;
        while(l<=r) {
            int mid = l + (r-l)/2;
            int L = inf,R= -inf;
            bool flag = false;
            for(int i = 2; i <= n; i++) {
                if(height[i]>=mid) {        //按照 mid 分组
                    L = min(L,sa[i]);
                    L = min(L,sa[i-1]);
                    R = max(R,sa[i]);
                    R = max(R,sa[i-1]);
                }
                else
                {
                    if(L+mid+1<=R) {
                        flag = true;
                        ans = mid;
                    }
                    L = inf;
                    R = -inf;
                }
            }
    ​
            if(L+mid+1<=R) flag = true;
            if(flag) l = mid+1;
            else r = mid - 1;
        }
    ​
        printf("%d
    ",ans);
    ​
        return 0;
    }
    View Code

    例题四:可重叠k次最长重复子串(pku3261)

    给定一个字符串,求至少出现 k 次的最长重复子串,这 k 个子串可以重叠。

    分析:此题可以重叠,二分答案,将后缀分组,由例题三和例题二,可以看出,只要判断是否存在一个分组使得其中元素>=k(重复k次嘛~~~)

    
    
    int main()
    {
        freopen("in.txt","r",stdin);
    ​
        scanf("%s",str);
        int n = strlen(str);
        int k;
        scanf("%d",&k);
    ​
        for(int i = 0; i < n; i++) r[i] = str[i] - 'a' + 1;
    ​
        da(r,sa,n+1,300);
        calheight(r,sa,n);
    ​
        for(int i =2 ; i<= n;i++)
            printf("%d ",height[i]);
        puts("");
    ​
        int l = 1,r= n;
        int ans = 0;
        while(l<=r) {
            int mid = l + (r-l)/2;
            int L = inf,R= -inf;
            bool flag = false;
            int cnt = 1;
            for(int i = 2; i <= n; i++) {
                if(height[i]>=mid) {        //按照 mid 分组
                    cnt++;
                }
                else
                {
                    if(cnt>=k) {
                        flag = true;
                        ans = mid;
                    }
                    L = inf;
                    R = -inf;
                    cnt = 1;
                }
            }
            if(flag) l = mid+1;
            else r = mid - 1;
        }
    ​
        printf("%d
    ",ans);
    ​
        return 0;
    }
    View Code

    例题五:子串的个数(spoj694,spoj705)

    给定一个字符串,求不相同的子串个数。

    这个题今年多校考过~~~~不过当时GG了。

    每一个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。

    每个后缀产生n-sa[i] 个前缀,其中有height[i] 已经与前面重复~~~

    
    
    int main()
    {
        freopen("in.txt","r",stdin);
    ​
        scanf("%s",str);
        int n = strlen(str);
    ​
        for(int i = 0; i < n; i++) r[i] = str[i] - 'a' + 1;
    ​
        da(r,sa,n+1,300);
        calheight(r,sa,n);
    ​
        for(int i =2 ; i<= n;i++)
            printf("%d ",height[i]);
        puts("");
    ​
        int cnt = n - sa[1];
    ​
        for(int i = 2; i <= n; i++)
            cnt = cnt + n - sa[i] - height[i];
        printf("%d
    ",cnt);
    ​
        return 0;
    }
    View Code
    
    

    例题六:最长回文子串(ural11297)

    给定一个字符串,求最长回文子串。

    先搞一发Manacher;

    #include <bits/stdc++.h>
    using namespace std;
    ​
    const int maxn = 1000008;
    ​
    char instr[maxn],str[maxn*2];
    int rad[maxn*2];
    ​
    ​
    int Manacher()
    {
        int i,j,maxx;
        int n = strlen(instr);
        memset(str,'#',sizeof(str));
        for(i=0;i<n;i++)
            str[(i+1)<<1] = instr[i];
    ​
        n = (n+1)<<1;
        str[n] = '$';
        int maxRad;
        maxRad = j = maxx = 0;
        for(i = 0;i<n;i++)
        {
            if(i<maxx)
                rad[i] = min(rad[2*j-i],maxx-i);
            else rad[i] = 1;
    ​
            while(str[i-rad[i]]==str[i+rad[i]])
                rad[i] ++;
            if(maxRad<rad[i])
                maxRad = rad[i];
            if(rad[i]+i>maxx)
            {
                j = i;
                maxx = rad[i] + i;
            }
    ​
        }
        return maxRad;
    ​
    }
    ​
    int main()
    {
        int t;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%s",instr);
            printf("%d
    ",Manacher()-1);
        }
        return 0;
    }
    View Code

    但是,后缀数组的思路是非常重要的,重要到关于两个字符串的题目,extend_kmp很难的话,那么基本上就是后缀数组了~~~

    分析:回文——将字符串倒着加到后面,枚举回文的中心点,而只要考虑回文的一侧,另一侧则在对称面,这两个后缀求LCP~~~

    int main()
    {
        freopen("in.txt","r",stdin);
    ​
        scanf("%s",str);
    ​
        int n = strlen(str);
        int tmpn = n;
        str[n] = '$';
    ​
        for(int i = n+1,j=0; i <= 2*n; i++,j++)
            str[i] = str[n-1-j];
    ​
        puts(str);
    ​
        for(int i = 0; i < n; i++) r[i] = str[i] - 'a' + 2;
        r[n] = 1;
        for(int i = n+1; i < 2*n+1; i++) r[i] = str[i] - 'a' + 2;
        n = strlen(str);
    //
    //    da(r,sa,n+1,300);
    //    calheight(r,sa,n);
    //
    //    for(int i =2 ; i<= n;i++)
    //        printf("%d ",height[i]);
    //    puts("");
    ​
    ​
        da(r,sa,n,300);
        calheight(r,sa,n);
    ​
        for(int i = 1; i <= n; i++)
            printf("%d ",sa[i]);
        puts("");
    ​
        init(n);
    ​
        int ans = 0;
        for(int i = 0; i < tmpn; i++) {     //枚举中心
            int l = i;
            int r = n - i - 1;
            l = ranks[l];
            r = ranks[r];
    ​
            l++;
    ​
            int lcp = cal(l,r);     //回文是奇数
            if(lcp*2-1>ans) {
                ans = lcp *2 -1;
            }
    ​
            l = i;
            if(l!=tmpn) {
                l++;
                r = n - l - 1;
                l = ranks[l];
                r = ranks[r];
                l++;
                lcp = cal(l,r);
                ans = max(ans,lcp*2);
            }
    ​
    ​
        }
        printf("%d
    ",ans);
    ​
    ​
        return 0;
    }
    View Code
  • 相关阅读:
    DB-MySQL:MySQL 正则表达式
    DB-MySQL:MySQL 事务
    DB-MySQL:MySQL 索引
    DB-MySQL:MySQL 临时表
    DB-MySQL:MySQL 复制表
    DB-MySQL:MySQL 序列使用
    DB-MySQL:MySQL 处理重复数据
    DB-MySql:MySQL 及 SQL 注入
    mysql
    PHP+jQuery 注册模块的改进之一:验证码存入SESSION
  • 原文地址:https://www.cnblogs.com/TreeDream/p/7707061.html
Copyright © 2020-2023  润新知