后缀数组模板题总结
Posted philo-zhou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀数组模板题总结相关的知识,希望对你有一定的参考价值。
出大问题,我原来学了几天,然后刷了七八题吧,之后将近半年一次没用到, 今天回头一想后缀数组??????????
所以来总结一下,省的模板题都不会了。
1 POJ - 3261:找出出现k次的可重叠的最长子串的长度。 https://vjudge.net/contest/283743#problem/D
思路:直接二分次数就行了,在判断里面看是连续>=k的最多有几个。
#include <iostream> #include <cstring> #include <cstdio> #include <cmath> #include <cstdlib> #define ll long long #define rep(i, a, b) for(int i = a; i <= b; i++) #define pb push_back #define mp make_pair using namespace std; const int mod = 1e9 + 7; const ll INF = 1e15; const double eps = 1e-8; const int maxn = 2e5 + 10; int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn]; int n, m; int s[maxn]; void get_sa(){ int tt; rep(i, 1, n) c[x[i] = s[i]]++; rep(i, 2, m) 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; rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k; rep(i, 0, m) c[i] = 0; rep(i, 1, n) c[x[i]]++; rep(i, 2, m) c[i] += c[i - 1]; for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0; rep(i, 0, n) { tt = x[i]; x[i] = y[i]; y[i] = tt; } x[sa[1]] = 1, num = 1; rep(i, 2, n) 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; } } void get_height(){ int k = 0; rep(i, 1, n) rk[sa[i]] = i; rep(i, 1, n){ if(rk[i] == 1) continue; if(k) k--;//h[i] >= h[i - 1] - 1 int j = sa[rk[i] - 1]; while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++; height[rk[i]] = k; } } bool judge(int len, int k){ int have = 1; rep(i, 2, n){ if(height[i] >= len) have++; else have = 1; if(have >= k) return 1; } return 0; } int main(){ int k; scanf("%d%d",&n, &k); m = 1e5; rep(i, 1, n){ scanf("%d",&s[i]); m = max(m, ++s[i]); } get_sa(); get_height(); int l = 1, r = n; int ans = 1; while(l <= r){ int mid = (l+r) / 2; if(judge(mid, k)){ ans = mid; l = mid+1; } else r = mid-1; } cout << ans << endl; return 0; }
2 SPOJ - DISUBSTR:不同子串的个数。 https://vjudge.net/contest/283743#problem/E
思路:所有的子串和是(n+1)*n/2,我们现在还需要个重复的个数,这样总的减去重复的就是我们要的。考虑height数组含义,和上一后缀的最长公共前缀的长度。
举个例子:
后缀sa[i-1]: aaabbd
后缀 sa[i] : aabbd
重复的是aa,现在我们要减去aa有多少个不同的子串,然后你发现这个重复的一定是同一个字母,不可能出现aaaab这种情况,因为这是后缀,你可以随便试试。这种串的子串个数就等于串的长度,也就是height。
那么答案出来了ans = (n+1)*n/2 - height[2 ~ n]; 因为height[1]是排名1的与上一位的最长公共前缀,所以它前边都没有。
#include <bits/stdc++.h> #define ll long long #define rep(i, a, b) for(int i = a; i <= b; i++) #define pb push_back #define mp make_pair using namespace std; const int mod = 1e9 + 7; const ll INF = 1e15; const double eps = 1e-8; const int maxn = 2e3 + 10; int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn]; int n, m; char s[maxn]; void get_sa(){ int tt; rep(i, 0, m) c[i] = 0; rep(i, 1, n) c[x[i] = s[i]]++; rep(i, 2, m) 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; rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k; rep(i, 0, m) c[i] = 0; rep(i, 1, n) c[x[i]]++; rep(i, 2, m) c[i] += c[i - 1]; for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0; rep(i, 0, n){ tt = x[i]; x[i] = y[i]; y[i] = tt; } x[sa[1]] = 1, num = 1; rep(i, 2, n) 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; } } void get_height(){ int k = 0; rep(i, 1, n) rk[sa[i]] = i; rep(i, 1, n){ if(rk[i] == 1) continue; if(k) k--;//h【i】 >= h[i - 1] - 1 int j = sa[rk[i] - 1]; while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++; height[rk[i]] = k; } } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%s", s + 1); m = 122, n = strlen(s + 1); get_sa(); get_height(); int ans = (n+1)*n / 2; rep(i, 2, n) ans -= height[i]; printf("%d ",ans); } return 0; }
3 POJ - 2406:给出一个串,是由它的一个子串重复k次得到的,问k最大是多少。https://vjudge.net/contest/283743#problem/G
思路:第一种方法直接用next数组,求过next数组后直接输出 if(len % (len - ne[len]) == 0) printf("%d
",len / (len-ne[len])); else printf("1
");
第二种就是后缀数组,自己没写,不加了。
4 POJ - 2774:求两个串的最长公共子串。 https://vjudge.net/contest/283743#problem/J
思路:先用个分隔符将两个字符串连起来,对这个新串跑一遍后缀数组,找最大的height值,注意还要满足条件:sa[i]与sa[i-1]分别在两串字符中,这样就保证height是两个串的公共前缀。
#include <iostream> #include <cstring> #include <cstdio> #include <cmath> #include <cstdlib> #define ll long long #define rep(i, a, b) for(int i = a; i <= b; i++) #define pb push_back #define mp make_pair using namespace std; const int mod = 1e9 + 7; const ll INF = 1e15; const double eps = 1e-8; const int maxn = 2e5 + 10; int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn]; int n, m; char s[maxn]; void get_sa(){ int tt; rep(i, 0, m) c[i] = 0; rep(i, 1, n) c[x[i] = s[i]]++; rep(i, 2, m) 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; rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k; rep(i, 0, m) c[i] = 0; rep(i, 1, n) c[x[i]]++; rep(i, 2, m) c[i] += c[i - 1]; for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0; rep(i, 0, n){ tt = x[i]; x[i] = y[i]; y[i] = tt; } x[sa[1]] = 1, num = 1; rep(i, 2, n) 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; } } void get_height(){ int k = 0; rep(i, 1, n) rk[sa[i]] = i; rep(i, 1, n){ if(rk[i] == 1) continue; if(k) k--;//h【i】 >= h[i - 1] - 1 int j = sa[rk[i] - 1]; while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++; height[rk[i]] = k; } } char st[maxn]; int main(){ scanf("%s %s",s+1,st+1); int len1 = strlen(s+1); int len2 = strlen(st+1); n = len1+len2+1; s[len1+1] = ‘#‘; rep(i, 1, len2){ s[len1+i+1] = st[i]; } // s[len1+len2+2] = 0; n = strlen(s+1); m = 122; get_sa(); get_height(); int l, r; int ma = 0; rep(i, 2, n){ l = sa[i-1]; r = sa[i]; if(l <= len1 && r <= len1) continue; else if(l > len1+1 && r > len1+1) continue; else ma = max(ma, height[i]); } printf("%d ",ma); return 0; }
5 POJ - 1743: https://vjudge.net/contest/283743#problem/C 不知道为啥当初学后缀数组时上来先写的这个,看了好久,现在思考过上边的几题后再想这个也很简单。
题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1~88范围内的整数,现在要找一个最长的重复主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:
1、长度至少为5个音符。
2、在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
3、重复出现的同一主题不能有公共部分。
思路:这题需要解决的问题有:重复子串的长度,重复部分不能有重合,转调。先把会的先想出来,重复子串的长度直接用height判断,不能有重合直接用sa判断位置是否有重合。就差这个转调了。
序列每个音符都加上或者减去同一个整数值,这不得不想到差分,这样就很容易想到我们只需要把数组预处理成差分就行了,因为s[i] - s[i-1]可能有负数,这样求sa就会有问题,都加上个数让数组不为负就行了,因为这个最大是88,差值不会超过88,所以我就加了个88,不用纠结于此。以上问题都解决了,怎么得到最长的?肯定是二分啊,明显满足二分属性。
#include <iostream> #include <cstring> #include <cstdio> #include <cmath> #include <cstdlib> #define ll long long #define rep(i, a, b) for(int i = a; i <= b; i++) #define pb push_back #define mp make_pair using namespace std; const int mod = 1e9 + 7; const ll INF = 1e15; const double eps = 1e-8; const int maxn = 1e5 + 10; int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn]; //rk[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP int n, m; int s[maxn]; void get_sa(){ int tt; rep(i, 0, n) c[i] = 0; rep(i, 1, n) c[x[i] = s[i]]++; rep(i, 2, m) 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; rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k; rep(i, 0, m) c[i] = 0; rep(i, 1, n) c[x[i]]++; rep(i, 2, m) c[i] += c[i - 1]; for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0; rep(i, 0, n){ tt = x[i]; x[i] = y[i]; y[i] = tt; } x[sa[1]] = 1, num = 1; rep(i, 2, n) 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; } } void get_height(){ int k = 0; rep(i, 1, n) rk[sa[i]] = i; rep(i, 1, n){ if(rk[i] == 1) continue; if(k) k--; int j = sa[rk[i] - 1]; while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++; height[rk[i]] = k; } } bool judge(int k){ int ma = sa[1], mi = sa[1]; rep(i, 2, n){ if(height[i] < k) mi = ma = sa[i]; else{ mi = min(sa[i], mi); ma = max(sa[i], ma); if(ma - mi + 1 >= k) return 1; } } return 0; } int main(){ while(scanf("%d",&n) && n){ rep(i, 1, n) scanf("%d",&s[i]); m = 0; rep(i, 1, n-1){ s[i] = s[i+1] - s[i] + 88; m = max(m, s[i]); } s[n] = 0; n--; get_sa(); get_height(); int l = 4, r = n/2; int ans = 0; while(l <= r){ int mid = (l+r) / 2; if(judge(mid)){ ans = mid; l = mid+1; } else r = mid-1; } // cout << ans << endl; if(ans >= 4) printf("%d ",ans+1); else printf("0 "); } return 0; }
以上是关于后缀数组模板题总结的主要内容,如果未能解决你的问题,请参考以下文章