码迷,mamicode.com
首页 > 编程语言 > 详细

后缀数组专题

时间:2017-09-10 21:50:04      阅读:201      评论:0      收藏:0      [点我收藏+]

标签:def   one   print   display   ash   表示   logs   元素   元组   

后缀数组基本模板

①倍增法(时间O(NlogN),空间O(N))

 

技术分享
 1 #include<iostream>
 2 using namespace std;
 3 const int maxl = 100010;
 4 char s[maxl];
 5 int totlen;
 6 int r2[maxl], cc[maxl],SA[maxl], RANK[maxl], Height[maxl];
 7 //r2:以第二关键字对后缀排序所得的辅助数组
 8 //cc:计数排序辅助数组
 9 //RANK:RANK数组,RANK[i]表示后缀i~totlen-1(Suffix[i])在所有后缀中的排名
10 //Height[i]:表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀 
11 //SA[i]:后缀数组,满足Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名为i的后缀为Suffix[SA[i]] (与Rank是互逆运算)
12 bool Cmp(int *Rank, int idx1, int idx2, int len)
13 {//比较两个串是否相同,比较两个关键字
14     int a1 = Rank[idx1];
15     int b1 = Rank[idx2];
16     int a2 = (idx1 + len >= totlen ? -1 : Rank[idx1 + len]);
17     int b2 = (idx2 + len >= totlen ? -1 : Rank[idx2 + len]);
18     return a1 == b1&&a2 == b2;
19 }
20 void Build_SA()
21 {
22     int m = 26;// 单字符rank的范围
23     //计数排序
24     for (int i = 0; i < m; i++) cc[i] = 0;
25     for (int i = 0; i < totlen; i++) ++cc[RANK[i] = (s[i] - a)];
26     for (int i = 1; i < m; i++) cc[i] += cc[i - 1];
27     for (int i = totlen - 1; i >= 0; --i) SA[--cc[RANK[i]]] = i;
28 
29     for (int k = 1; k <= totlen; k <<= 1)
30     {
31         int p = 0;
32 
33         for (int i = totlen - k; i < totlen; i++)
34         {//第二关键字为空的后缀放在最开头
35             r2[p++] = i;
36         }
37         for (int i = 0; i < totlen; i++)
38         {//接着放第二关键字不为空的
39             if (SA[i] >= k) r2[p++] = SA[i] - k;
40         }
41         //计数排序
42         for (int i = 0; i < m; i++) cc[i] = 0;
43         for (int i = 0; i < totlen; i++) ++cc[RANK[i]];
44         //for (int i = 0; i < totlen; i++) ++cc[RANK[r2[i]]];
45         for (int i = 1; i < m; i++) cc[i] += cc[i - 1];
46         for (int i = totlen - 1; i >= 0; --i) SA[--cc[RANK[r2[i]]]] = r2[i];
47 
48         swap(RANK, r2);
49         m = 1;
50         RANK[SA[0]] = 0;
51         //r2指向旧的rank数组
52         for (int i = 1; i < totlen; i++) RANK[SA[i]] = (Cmp(r2, SA[i], SA[i - 1], k) ? m - 1 : m++);
53 
54         if (m >= totlen) break;
55     }
56 }
57 void Build_Height()
58 {
59     for (int i = 0; i < totlen; i++)  RANK[SA[i]] = i;
60     Height[0] = 0;
61     int k = 0;
62     for (int i = 0; i < totlen; i++)
63     {
64         if (!RANK[i]) continue;
65         int j = SA[RANK[i] - 1];
66         if (k) k--;
67         while (s[i + k] == s[j + k]) k++;
68         Height[RANK[i]] = k;
69     }
70 }
71 
72 int main()
73 {
74     scanf("%s", s);
75     totlen = strlen(s);
76     Build_SA();
77     Build_Height();
78     for (int i = 0; i < totlen; i++) printf("%d ", SA[i]);
79     printf("\n");
80     for (int i = 0; i < totlen; i++) printf("%d ", Height[i]);
81     printf("\n");
82 
83 
84     return 0;
85 }
86 //abcabcabcabc
87 //9 6 3 0 10 7 4 1 11 8 5 2
88 //0 3 6 9 0 2 5 8 0 1 4 7
View Code

 

———————————————————————————————————————————————————

———————————————————————————————————————————————————

1、poj 1743 Musical Theme

  题意:给你一个长度为n(1<=n<=20000)的数字串。如果一个串在母串出现的次数大于一次那么这个串就是母串的重复子串。子串的每个位置同时加上一个数字重复出现在另一个位置也算重复。先在问这个母串最长的不相交即没有重复元素的重复子串的最大长度。

  思路:后缀数组。对于题目所给的加上一个数字重复的情况。可以令s[i]=s[i+1]-s[i],直接转换成求字符串最长公共前缀。

  求不相交重复子串:二分最大长度k。然后再用后缀数组判定。这样我们就可以将height分组,其中每组的后缀之间的height 值都不小于k。容易看出,有希望成为最长公共前缀不小于k 的两个后缀一定在同一组。然后对于每组后缀,只须判断每个后缀的sa 值的最大值和最小值之差是否不小于k。如果有一组满足,则说明存在,否则不存在。

技术分享
  1 //题意:给你一个长度为n(1<=n<=20000)的数字串。如果一个串在母串出现的次数大于一次那么这个串就是母串的重复子串。子串的每个位置同时加上一个数字重复出现在另一个位置也算重复。先在问这个母串最长的不相交即没有重复元素的重复子串的最大长度。
  2 #include<iostream>
  3 #define min(a,b) ((a)<(b)?(a):(b))
  4 #define max(a,b) ((a)>(b)?(a):(b))
  5 using namespace std;
  6 const int MAX = 20050;
  7 
  8 int n, num[MAX];
  9 int sa[MAX], Rank[MAX], height[MAX];
 10 int tp[MAX], wb[MAX], wv[MAX], tax[MAX];
 11 //sa:所有后缀的字典序中排第i位的在原串的起始位置为sa[i]
 12 //Rank:原串中第i个位置开始的后缀在字典序的排名
 13 //heiht:字典序中第i个和i-1个的后缀的最长公共前缀
 14 //tp:rank的辅助数组(计数排序中的第二关键字),与SA意义一样。
 15 //wb:
 16 //wv:
 17 //tax:基数排序辅助数组
 18 int cmp(int *r, int a, int b, int l)
 19 {//通过二元组两个下标的比较,确定两个子串是否相同
 20     return r[a] == r[b] && r[a + l] == r[b + l];
 21 }
 22 
 23 void getsa(int *r, int n, int m)
 24 {//m为ASCII码的范围 
 25     int i, j, p, *x = tp, *y = wb, *t;
 26     //基数排序
 27     for (i = 0; i < m; i++) tax[i] = 0;
 28     for (i = 0; i < n; i++) tax[x[i] = r[i]] ++;
 29     for (i = 1; i < m; i++) tax[i] += tax[i - 1];
 30     for (i = n - 1; i >= 0; i--) sa[--tax[x[i]]] = i;//倒着枚举保证相对顺序  
 31 
 32     for (j = 1, p = 1; p < n; j *= 2, m = p)
 33     {//把子串长度翻倍,更新rank
 34         //j:当前一个字串的长度
 35         //m:当前离散后的排名种类数
 36         for (p = 0, i = n - j; i < n; i++) y[p++] = i;
 37         for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;//按第二关键字排序.y[i]表示第二关键字排名第i的后缀起始位置  
 38 
 39         for (i = 0; i < n; i++) wv[i] = x[y[i]];//暂存
 40 
 41         for (i = 0; i < m; i++) tax[i] = 0;
 42         for (i = 0; i < n; i++) tax[wv[i]] ++;//x[i]表示起始位置为i的后缀的第一关键字排序
 43         for (i = 1; i < m; i++) tax[i] += tax[i - 1];
 44         for (i = n - 1; i >= 0; i--) sa[--tax[wv[i]]] = y[i];//接着按第一关键字排序 
 45 
 46         for (t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i++)
 47         {////x[i]存排名第i后缀的排名 
 48             x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
 49         }
 50     }
 51 }
 52 
 53 void calHeight(int *r, int n)
 54 {
 55     int i, j, k = 0;
 56     for (i = 1; i <= n; i++) Rank[sa[i]] = i;
 57     for (i = 0; i < n; height[Rank[i++]] = k)
 58     {
 59         for (k ? k-- : 0, j = sa[Rank[i] - 1]; r[i + k] == r[j + k]; k++);
 60     }
 61 }
 62 
 63 bool valid(int len)
 64 {
 65     int i = 2, ma, mi;//区间下界和上界  
 66     while (1)
 67     {
 68         while (i <= n && height[i] < len) i++;
 69         if (i > n) break;
 70         ma = sa[i - 1];
 71         mi = sa[i - 1];
 72         while (i <= n && height[i] >= len)
 73         {
 74             ma = max(ma, sa[i]);
 75             mi = min(mi, sa[i]);
 76             i++;
 77         }
 78         if (ma - mi >= len) return true;
 79     }
 80     return false;
 81 }
 82 
 83 int main()
 84 {
 85     int i, ans;
 86     while (scanf("%d", &n) && n != 0)
 87     {
 88         for (i = 0; i < n; i++)
 89         {
 90             scanf("%d", &num[i]);
 91         }
 92         if (n < 10)
 93         {//如果小于10,则不相交重复子串字串长度不超过5,不符合题意
 94             printf("0\n");
 95             continue;
 96         }
 97         n--;
 98         for (i = 0; i < n; i++)
 99         {
100             num[i] = num[i + 1] - num[i] + 89;
101         }
102         num[n] = 0;
103         getsa(num, n + 1, 200);
104         calHeight(num, n);
105 
106         int low = 4, high = (n - 1) / 2, mid;
107         while (low < high)
108         {
109             mid = (low + high + 1) / 2;
110             if (valid(mid))
111             {
112                 low = mid;
113             }
114             else
115             {
116                 high = mid - 1;
117             }
118         }
119         ans = low < 4 ? 0 : low + 1;//加回1
120         printf("%d\n", ans);
121     }
122     return 0;
123 }
View Code

 2、UVA 12206  Stammering Aliens

  题意:给定一个序列,求出现次数至少为m、长度最长的子串的最大起始下标

  思路:对原串做完后缀数组后二分最大长度,对于每个二分值k,对height数组分组,如果某组中后缀数量大于等于m则找到这个组中sa[i]的最大值来更新答案值 

技术分享
  1 #include<iostream>
  2 #include<algorithm>
  3 #include<cstring>
  4 using namespace std;
  5 int k;
  6 
  7 //后缀数组模板
  8 const int maxl = 40010;
  9 char s[maxl];
 10 int totlen;
 11 int r2[maxl], cc[maxl], SA[maxl], RANK[maxl], Height[maxl];
 12 //r2:以第二关键字对后缀排序所得的辅助数组
 13 //cc:计数排序辅助数组
 14 //RANK:RANK数组,RANK[i]表示后缀i~totlen-1(Suffix[i])在所有后缀中的排名
 15 //Height[i]:表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀 
 16 //SA[i]:后缀数组,满足Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名为i的后缀为Suffix[SA[i]] (与Rank是互逆运算)
 17 bool Cmp(int *Rank, int idx1, int idx2, int len)
 18 {//比较两个串是否相同,比较两个关键字
 19     int a1 = Rank[idx1];
 20     int b1 = Rank[idx2];
 21     int a2 = (idx1 + len >= totlen ? -1 : Rank[idx1 + len]);
 22     int b2 = (idx2 + len >= totlen ? -1 : Rank[idx2 + len]);
 23     return a1 == b1&&a2 == b2;
 24 }
 25 void Build_SA()
 26 {
 27     int m = 26;// 单字符rank的范围
 28                //计数排序
 29     for (int i = 0; i < m; i++) cc[i] = 0;
 30     for (int i = 0; i < totlen; i++) ++cc[RANK[i] = (s[i] - a)];
 31     for (int i = 1; i < m; i++) cc[i] += cc[i - 1];
 32     for (int i = totlen - 1; i >= 0; --i) SA[--cc[RANK[i]]] = i;
 33 
 34     for (int k = 1; k <= totlen; k <<= 1)
 35     {
 36         int p = 0;
 37 
 38         for (int i = totlen - k; i < totlen; i++)
 39         {//第二关键字为空的后缀放在最开头
 40             r2[p++] = i;
 41         }
 42         for (int i = 0; i < totlen; i++)
 43         {//接着放第二关键字不为空的
 44             if (SA[i] >= k) r2[p++] = SA[i] - k;
 45         }
 46         //计数排序
 47         for (int i = 0; i < m; i++) cc[i] = 0;
 48         for (int i = 0; i < totlen; i++) ++cc[RANK[i]];
 49         //for (int i = 0; i < totlen; i++) ++cc[RANK[r2[i]]];
 50         for (int i = 1; i < m; i++) cc[i] += cc[i - 1];
 51         for (int i = totlen - 1; i >= 0; --i) SA[--cc[RANK[r2[i]]]] = r2[i];
 52 
 53         swap(RANK, r2);
 54         m = 1;
 55         RANK[SA[0]] = 0;
 56         //r2指向旧的rank数组
 57         for (int i = 1; i < totlen; i++) RANK[SA[i]] = (Cmp(r2, SA[i], SA[i - 1], k) ? m - 1 : m++);
 58 
 59         if (m >= totlen) break;
 60     }
 61 }
 62 void Build_Height()
 63 {
 64     for (int i = 0; i < totlen; i++)  RANK[SA[i]] = i;
 65     Height[0] = 0;
 66     int k = 0;
 67     for (int i = 0; i < totlen; i++)
 68     {
 69         if (!RANK[i]) continue;
 70         int j = SA[RANK[i] - 1];
 71         if (k) k--;
 72         while (s[i + k] == s[j + k]) k++;
 73         Height[RANK[i]] = k;
 74     }
 75 }
 76 
 77 
 78 
 79 int Judge(int x)
 80 {
 81     int ans = -1;
 82     for (int i = 0; i < totlen; i++)
 83     {
 84         if (totlen - SA[i] < x)continue;
 85         int tans = SA[i], cnt = 1;
 86         while (i + 1 < totlen&&Height[i + 1] >= x)
 87         {
 88             tans = max(tans, SA[i + 1]);
 89             cnt++;
 90             i++;
 91         }
 92         if (cnt >= k) ans = max(ans, tans);
 93     }
 94     return ans;
 95 }
 96 void Solve()
 97 {
 98     if (Judge(1) == -1)
 99     {
100         printf("none\n");
101         return;
102     }
103     int l = 1, r = r = totlen,ans;
104     while (l <= r)
105     {
106         int mid = (l + r) / 2;
107         int tmp = Judge(mid);
108         if ( tmp!= -1) l = mid + 1,ans=tmp;
109         else r = mid - 1;
110     }
111     printf("%d %d\n", r, ans);
112 }
113 int main()
114 {
115     while (~scanf("%d", &k) && k)
116     {
117         scanf("%s", s);
118         totlen = strlen(s);
119         Build_SA();
120         Build_Height();
121         //for (int i = 0; i < totlen; i++) printf("%d ", SA[i]);
122         //printf("\n");
123         //for (int i = 0; i < totlen; i++) printf("%d ", Height[i]);
124         //printf("\n");
125         Solve();
126     }
127     return 0;
128 }
View Code

 

后缀数组专题

标签:def   one   print   display   ash   表示   logs   元素   元组   

原文地址:http://www.cnblogs.com/ivan-count/p/7368154.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!