【后缀数组之应用】【待续】

【最长重复子串问题】

可重叠最长重复子串 -- POJ 3261

Disc: 给出包含n个元素的数组a,问其中最长可重叠重复子串的长度,要求该子串至少重复k次;输入保证至少存在一个重复k次的最长子串;

Tips: 二分查找子串的长度,注意对该长度的子串是否存在K个重复子串的判定方法;

代码:

  1 /*
  2 Problem: POJ 3261
  3 Tips: 可重复最长重复子串,后缀数组,二分
  4 Time: 172ms
  5 Date: 2015.8.31
  6 */
  7 #include <iostream>
  8 #include <cstring>
  9 #include <cstddef>
 10 #include <cstdio>
 11 #include <string>
 12 #include <algorithm>
 13 const int maxn = 100005;
 14 const int maxm = 1000005;
 15 int wa[maxm],wb[maxn],wv[maxn],ws[maxm];
 16 int sa[maxn], rank[maxn],height[maxn];
 17 int cmp(int *rank, int a,int b,int l)
 18 {
 19     return rank[a]==rank[b] && rank[a+l]==rank[b+l];
 20 }
 21 void debug1(int* r, int n)
 22 {
 23     for(int i = 0; i < n; i++)
 24     {
 25         printf("sa[%d]: %d
", i, sa[i]);
 26         int p = sa[i];
 27         for(; p < n; p++)
 28             printf("%d ", r[p]);
 29         printf("
");
 30     }
 31 }
 32 void debug2(int* r, int n)
 33 {
 34     for(int i = 0; i < n; i++)
 35     {
 36         printf("height[rank[%d]]: %d
", i, height[rank[i]]);
 37     }
 38 }
 39 void da(int *r,int n,int m)
 40 {
 41     int i, k, p, *x=wa, *y=wb, *t;
 42     for(i=0;i<m;i++) ws[i] = 0;
 43     for(i=0;i<n;i++) ws[x[i] = r[i]]++;
 44     for(i=1;i<m;i++) ws[i] += ws[i-1];
 45     for(i=n-1;i>=0;i--) sa[--ws[x[i]]] = i;
 46     for(k=1, p=1; p<n; k*=2, m=p)
 47     {
 48         for(p=0,i=n-k;i<n;i++) y[p++]=i;
 49         for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
 50         for(i=0;i<n;i++) wv[i]=x[y[i]];
 51         for(i=0;i<m;i++) ws[i]=0;
 52         for(i=0;i<n;i++) ws[wv[i]]++;
 53         for(i=1;i<m;i++) ws[i]+=ws[i-1];
 54         for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
 55         t=x,x=y,y=t;
 56         for(p=1,x[sa[0]]=0,i=1;i<n;i++)
 57             x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?p-1:p++;
 58     }
 59     //debug1(r, n);
 60     return;
 61 }
 62 void calheight(int *r,int n)
 63 {
 64     int i,j,k=0;
 65     for(i=1;i<=n;i++) rank[sa[i]]=i;
 66     for(i=0;i<n;height[rank[i++]]=k)
 67         for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++)
 68             ;
 69     //debug2(r, n);
 70     return;
 71 }
 72 bool check(int mid, int n, int k) //Notice!
 73 {
 74     int i = 1; //height[0] = 0;
 75     while(1)
 76     {
 77         while(i <= n && height[i] < mid)    i++;
 78         if(i == n+1) break;
 79         int cnt = 1;
 80         while(i <= n && height[i] >= mid)   i++, cnt++;
 81         if(cnt >= k) return true;
 82     }
 83     return false;
 84 }
 85 int bin_search(int n, int k)
 86 {
 87     int l = 1, r = n, mid;
 88     int ans = 0;
 89     while(l <= r)
 90     {
 91         mid = l+(r-l)/2;
 92         if(check(mid, n, k))
 93             ans = mid, l = mid+1;
 94         else
 95             r = mid-1;
 96     }
 97     return ans;
 98 }
 99 int main()
100 {
101     int n, k, r[maxn];
102     scanf("%d%d", &n, &k);
103     for(int i = 0; i < n; i++) {scanf("%d", &r[i]); r[i]++;}
104     r[n] = 0;
105     da(r,n+1,maxm);
106     calheight(r,n);
107     int ans = bin_search(n, k);
108     printf("%d
", ans);
109     return 0;
110 }
View Code

不可重叠最长重复子串 -- POJ 1743

Disc: 给出一段长度为n的数组,其元素大小在1-88之间,现在要求出这个串中最长的不重复的两个相同的子串,满足要求的子串的定义是长度不短于5,其两个子串可以通过分别加上某一值或减去某一值得到,两子串不能够重叠。

Tips: 转化为相邻两元素差数组,求其不可重叠最长重复子串,且长度不可小于4;

     二分查找子串的长度,将height[]数组分组,判断连续的height[]大于等于k的一段中sa[i] -sa[j] 是否 >= k (height[i , j+1] >= k),若存在此情况,则证明该长度下存在不重叠重复子串;

代码:

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <cstdio>
 5 #include <string>
 6 #include <algorithm>
 7 const int maxn = 100001;
 8 int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
 9 int rank[maxn],height[maxn],sa[maxn];
10 int cmp(int *rank, int a,int b,int l)
11 {
12     return rank[a]==rank[b] && rank[a+l]==rank[b+l];
13 }
14 void da(int *r,int n,int m)
15 {
16     int i, k, p, *x=wa, *y=wb, *t;
17 
18     for(i=0;i<m;i++) ws[i] = 0;
19     for(i=0;i<n;i++) ws[x[i] = r[i]]++;
20     for(i=1;i<m;i++) ws[i] += ws[i-1];
21     for(i=n-1;i>=0;i--) sa[--ws[x[i]]] = i;
22     for(k=1, p=1; p<n; k*=2, m=p)
23     {
24         p=0; for(i=n-k;i<n;i++) y[p++]=i;
25         for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
26         for(i=0;i<n;i++) wv[i]=x[y[i]];
27         for(i=0;i<m;i++) ws[i]=0;
28         for(i=0;i<n;i++) ws[wv[i]]++;
29         for(i=1;i<m;i++) ws[i]+=ws[i-1];
30         for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
31         t=x,x=y,y=t;
32         for(p=1,x[sa[0]]=0,i=1;i<n;i++)
33             x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?p-1:p++;
34     }
35     return;
36 }
37 
38 void calheight(int *r,int n)
39 {
40     int i,j,k=0;
41     for(i=1;i<=n;i++) rank[sa[i]]=i;
42     for(i=0;i<n;height[rank[i++]]=k)
43         for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++)
44             ;
45     return;
46 }
47 bool check(int k, int n)
48 {
49     for(int i = 1; i <= n; i++)
50     {
51         if(height[i] < k) continue;
52         for(int j = i-1; j >= 1; j--)
53         {
54             if(abs(sa[i]-sa[j]) >= k) return true;
55             if(height[j] < k) break;
56         }
57     }
58     return false;
59 }
60 int bin_search(int n)
61 {
62     int l = 1, r = n, mid, ans = 0;
63     while(l <= r)
64     {
65         mid = l+(r-l)/2;
66         if(check(mid, n))
67             ans = mid, l = mid+1;
68         else
69             r = mid-1;
70     }
71     return ans;
72 }
73 
74 int main()
75 {
76     int n, r[maxn];
77     while(~scanf("%d", &n) && n)
78     {
79         n--;
80         int a, b;   scanf("%d", &a);
81         for(int i = 0; i < n; i++)
82         {
83             scanf("%d", &b);
84             r[i] = b-a+100;
85             a = b;
86         }
87         r[n] = 0;
88 
89         if(n<10) {printf("0
"); continue;}
90 
91         da(r,n+1,200);
92         calheight(r,n);
93 
94         int ans = bin_search(n);
95         printf("%d
", ans>=4 ? ++ans : 0);
96     }
97     return 0;
98 }
View Code

【回文子串问题】

最长回文子串 -- URAL 1297

Disc: 给出字符串,输出其最长回文子串

Tips: 对于字符串上的每个位置i,如果知道从i开始的后缀和到i为止的前缀反转后的字符串的最长公共前缀的话,就知道了以第i个字符为对称中心的最长回文的长度了;
        因此我们用在S中不会出现的字符('$')将S和S反转后的字符串拼接起来,得到字符串S',再计算S'的后缀数组和height[]数组。

    对height数组进行相应区间查询最小值RMQ,奇数回文串即区间[rank[i], rank[n-i-1]]中height的最小值,偶数回文串即区间[rank[i], rank[n-i]]中height的最小值

代码:

  1 /*
  2 Problem: URAL 1297
  3 Disc: 给出字符串,求最长回文子串
  4 Tips: Suffix Array, height[], RMQ
  5       对于字符串上的每个位置i,如果知道从i开始的后缀和到i为止的前缀反转后的字符串的最长公共前缀的话,就知道了以第i个字符为对称中心的最长回文的长度了;
  6       因此我们用在S中不会出现的字符('$')将S和S反转后的字符串拼接起来,得到字符串S',再计算S'的后缀数组。
  7 Date: 2015.9.1
  8 */
  9 #include<iostream>
 10 #include<cstdio>
 11 #include<cstring>
 12 using namespace std;
 13 const int maxn = 1002;
 14 int sa[maxn], rank[maxn], height[maxn];
 15 int wa[maxn], wb[maxn], wv[maxn], wd[maxn];
 16 int cmp(int *r, int a, int b, int l){
 17     return r[a] == r[b] && r[a+l] == r[b+l];
 18 }
 19 void da(int *r, int n, int m){          //  倍增算法 r为待匹配数组  n为总长度 m为字符范围
 20     int i, j, p, *x = wa, *y = wb, *t;
 21     for(i = 0; i < m; i ++) wd[i] = 0;
 22     for(i = 0; i < n; i ++) wd[x[i]=r[i]] ++;
 23     for(i = 1; i < m; i ++) wd[i] += wd[i-1];
 24     for(i = n-1; i >= 0; i --) sa[-- wd[x[i]]] = i;
 25     for(j = 1, p = 1; p < n; j *= 2, m = p){
 26         for(p = 0, i = n-j; i < n; i ++) y[p ++] = i;
 27         for(i = 0; i < n; i ++) if(sa[i] >= j) y[p ++] = sa[i] - j;
 28         for(i = 0; i < n; i ++) wv[i] = x[y[i]];
 29         for(i = 0; i < m; i ++) wd[i] = 0;
 30         for(i = 0; i < n; i ++) wd[wv[i]] ++;
 31         for(i = 1; i < m; i ++) wd[i] += wd[i-1];
 32         for(i = n-1; i >= 0; i --) sa[-- wd[wv[i]]] = y[i];
 33         for(t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i ++){
 34             x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1: p ++;
 35         }
 36     }
 37 }
 38 void calheight(int *r, int n){           //  求height数组。
 39     int i, j, k = 0;
 40     for(i = 1; i <= n; i ++) rank[sa[i]] = i;
 41     for(i = 0; i < n; height[rank[i ++]] = k){
 42         for(k ? k -- : 0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; k ++);
 43     }
 44 }
 45 
 46 /*******************非模板区***********************/
 47 int Log2[maxn];
 48 void init()
 49 {
 50     Log2[0] = -1; Log2[1] = 0;
 51     int i, j;
 52     for(i = 2, j = 0; i < maxn; i++)
 53     {
 54         if(i > (1<<(j+1))) j++;
 55         Log2[i] = j;
 56     }
 57 }
 58 int dp[maxn][20];
 59 void initRMQ(int n)
 60 {
 61     for(int i = 1; i <= n; i++) dp[i][0] = height[i];
 62     for(int j = 1; j <= 20; j++)
 63         for(int i = 1; i + (1 << (j-1)) <= n; i++)
 64             dp[i][j] = min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]); //注意是height[]的最小值
 65 }
 66 int query(int L, int R)
 67 {
 68     if(L > R) swap(L, R);
 69     L++;
 70     if(L == R) return dp[L][0];
 71     int len = R-L+1;
 72     int k = Log2[len]; //此处用的是Log2[]数组保存每个数的log值,当然也可以用下面的式子求得;
 73     //int k = (int)(log(des - src + 1.0) / log(2.0));
 74     return min(dp[L][k], dp[R-(1<<k)+1][k]);
 75 }
 76 void solve(char* s, int n)
 77 {
 78     initRMQ(n);
 79     int slen = strlen(s);
 80     int maxl = 1, l, len, start;
 81     for(int i = 0; i < slen; i++)
 82     {
 83         l = query(rank[i], rank[n-i-1]); //奇数回文串
 84         len = l*2-1;
 85         if(len > maxl)  maxl = len, start = i-l+1;
 86 
 87         l = query(rank[i], rank[n-i]);  //偶数回文串
 88         len = l*2;
 89         if(len > maxl)  maxl = len, start = i-l;
 90     }
 91     if(maxl == 1)   printf("%c
", s[0]);
 92     else
 93     {
 94         for(int i = start; i < start + maxl; i++)
 95             printf("%c", s[i]);
 96         printf("
");
 97     }
 98 }
 99 int main()
100 {
101     init();
102     char s[maxn];
103     int r[maxn*2];
104     while(~scanf("%s", s))
105     {
106         int len = strlen(s), n = 0;
107         for(int i = 0; i < len; i++) r[n++] = s[i];
108         r[n++] = '$';
109         for(int i = len-1; i >= 0; i--) r[n++] = s[i];
110         r[n] = 0;
111         da(r,n+1,128);
112         calheight(r,n);
113         solve(s,n);
114     }
115     return 0;
116 }
View Code

 【公共子串问题】

最长公共连续子串 --POJ 2774

Disc: 给定两个字符串A和B,求最长公共连续子串

Tips: 首先将两个字符串合并成一个字符串,中间由一个字符串中不可能出现的字符分割。通过二分枚举长度,然后在height分组中查看是否存在在两个不同串的后缀即可。

        也可以证明最长的公共子串所在后缀是相邻的,因此可以直接遍历一遍height数组,判定相邻的两个height数组是否属于不同的两个串。 

code:

 1 /*
 2 Problem: POJ 2774
 3 Tips: 最长公共连续子串
 4 Date: 2015.8.30
 5 */
 6 #include<iostream>
 7 #include<cstdio>
 8 #include<cstring>
 9 using namespace std;
10 const int maxn = 200002;
11 int sa[maxn], rank[maxn], height[maxn];
12 int wa[maxn], wb[maxn], wv[maxn], wd[maxn];
13 int cmp(int *r, int a, int b, int l){
14     return r[a] == r[b] && r[a+l] == r[b+l];
15 }
16 void da(int *r, int n, int m){          //  倍增算法 r为待匹配数组  n为总长度 m为字符范围
17     int i, j, p, *x = wa, *y = wb, *t;
18     for(i = 0; i < m; i ++) wd[i] = 0;
19     for(i = 0; i < n; i ++) wd[x[i]=r[i]] ++;
20     for(i = 1; i < m; i ++) wd[i] += wd[i-1];
21     for(i = n-1; i >= 0; i --) sa[-- wd[x[i]]] = i;
22     for(j = 1, p = 1; p < n; j *= 2, m = p){
23         for(p = 0, i = n-j; i < n; i ++) y[p ++] = i;
24         for(i = 0; i < n; i ++) if(sa[i] >= j) y[p ++] = sa[i] - j;
25         for(i = 0; i < n; i ++) wv[i] = x[y[i]];
26         for(i = 0; i < m; i ++) wd[i] = 0;
27         for(i = 0; i < n; i ++) wd[wv[i]] ++;
28         for(i = 1; i < m; i ++) wd[i] += wd[i-1];
29         for(i = n-1; i >= 0; i --) sa[-- wd[wv[i]]] = y[i];
30         for(t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i ++){
31             x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1: p ++;
32         }
33     }
34 }
35 void calheight(int *r, int n){           //  求height数组。
36     int i, j, k = 0;
37     for(i = 1; i <= n; i ++) rank[sa[i]] = i;
38     for(i = 0; i < n; height[rank[i ++]] = k){
39         for(k ? k -- : 0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; k ++);
40     }
41 }
42 void solve(int n, int l1, int l2)
43 {
44     int maxl = 0;
45     for(int i = 2; i < n; i++)
46     {
47         if(height[i] > maxl)
48         {
49             if(sa[i-1] >= 0 && sa[i-1] < l1 && sa[i] > l1)
50                 maxl = height[i];
51             if(sa[i] >= 0 && sa[i] < l1 && sa[i-1] > l1)
52                 maxl = height[i];
53         }
54     }
55     printf("%d
", maxl);
56 }
57 int main()
58 {
59     char s1[maxn], s2[maxn];
60     int s[maxn], r[maxn];
61     scanf("%s%s", s1, s2);
62     int l1 = strlen(s1), l2 = strlen(s2);
63     int n = 0;
64     for(int i = 0; i < l1; i++)
65         s[n++] = (s1[i]-'a'+1);
66     s[n++] = 28;
67     for(int i = 0; i < l2; i++)
68         s[n++] = (s2[i]-'a'+1);
69     s[n] = 0;
70     da(s,n+1,30);
71     calheight(s,n);
72     solve(n, l1, l2);
73     return 0;
74 }
View Code
原文地址:https://www.cnblogs.com/LLGemini/p/4772587.html