后缀数组是解决一系列字符串题目的利器,后缀数组中保留了这样的信息。sa[i]表示排名为第 i 位的后缀是从sa[i]开始的。通过倍增算法可以在O(nlogn)的时间复杂度内将所有的后缀进行排序。而height数组也是在处理问题中经常要使用到的,height[i]表示排名第 i 的后缀与排名第 i-1 位的后缀的最长公共前缀的长度。具体见代码。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 100005; int wa[N],wb[N],wv[N],ws[N]; 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; // 下面四行是对第一个字母的一个基数排序:基数排序其实就是记录前面有多少个位置被占据了 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; // 根据位置来排序,sa[x] = i,表示i位置排在第x位 // wa[x[i]]就是字符集0-x[i]共有多少字符占据了位置,减去自己的一个位置剩下的就是自己的排名了,排名从0开始 // 排名过程中主要的过程是对于处于相同字符的字符的排序,因为改变wa[x[i]]值得只会是本身,小于该字符的贡献值 // 是不变的,对于第一个字符相同的依据是位置关系,在后面将看到通过第二个关键字来确定相同字符的先后关系 // 这以后的排序都是通过两个关键字来确定一个串的位置,也即倍增思想 // 通过将一个串分解成两部分,而这两部分的位置关系我们都已经计算出来 for(j=1,p=1;p<n;j*=2,m=p) { for(p=0,i=n-j;i<n;i++) y[p++]=i; // 枚举的串是用于与i位置的串进行合并,由于i较大,因为匹配的串为空串 // 由于枚举的是长度为j的串,那么i位置开始的串将凑不出这个长度的串,因此第二关键字应该最小,这其中位置靠前的较小 for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; // sa[i]-j开头的串作为第二关键字与编号为sa[i]的串匹配,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(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;i++) // 对排好序的sa数组进行一次字符集缩小、常数优化 x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } return; } int rank[N],height[N]; void calheight(int *r,int *sa,int n) // 这里的n是原串的本来长度,即不包括新增的0 { int i,j,k=0; for(i=1;i<=n;i++) rank[sa[i]]=i; // 有后缀数组得到名次数组,排名第0的后缀一定是添加的0 for(i=0;i<n;height[rank[i++]]=k) // 以 i 开始的后缀总能够从以 i-1 开始的后缀中继承 k-1 匹配项出来 for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++); // 进行一个暴力的匹配,但是整个算法的时间复杂度还是O(n)的 return; } int main() { return 0; }
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 10005; int wa[N], wb[N], ws[N], wv[N]; int rank[N], height[N]; bool 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; 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 (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; k++); } int main() { return 0; }
PS:后面的题目,da函数中均写错了一个地方,即统计ws数组的时候,i 应该是从1开始的,模板已改正,后面的就不改了......
重复子串:字符串R在字符串L中至少出现两次,则称R是L的重复子串
1)可重复性最长重复子串:求出height数组最大的一个值即可。
2)不可重复最长重复子串:二分枚举长度,将原问题转化为一个判定性的问题,若枚举的长度为k那么根据height值进行分组,连续的height值大于等于k的分为一组,通过查看一组内部的最大后缀编号和最小后缀编号差值是否大于等于k来判定。
POJ-1743 Musical Theme
题意:给定若干个数字,其大小在1-88之间,现在要出要出这个串中最长的不重复的两个相同的子串,满足要求的子串的定义是长度不短于5,其两个子串可以通过分别加上某一值或减去某一值得到,串不能够有公共部分。
分析:首先通过该串的后一项减去前一项得到一个反应差值的新序列,然后再对这个新序列求sa数组即height数组,使用二分答案的方式来解决这个问题,需要注意的是转化为差值之后,在计算编号差的时候不能够取等号,否则将会使得共用一个元素的情况发生。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int inf = 0x3f3f3f3f; const int N = 20005; int n; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], wv[N], ws[N]; bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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; } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?--k:0, j=sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } bool check(int mid) { int Min, Max; for (int i = 2; i <= n; ++i) { Min = sa[i-1], Max = sa[i-1]; while (height[i]>= mid) { Min = min(Min, sa[i]); Max = max(Max, sa[i]); ++i; } if (Max - Min > mid) return true; // 由于该题的限制不能够取到等于 } return false; } void solve() { int l = 4, r = n / 2, ret = -1; while (l <= r) { int mid = (l + r) >> 1; if (check(mid)) { l = mid + 1; ret = mid; } else { r = mid - 1; } } printf("%d ", ret + 1); } int main() { while (scanf("%d", &n), n) { for (int i = 0; i < n; ++i) { scanf("%d", &seq[i]); } if (n <= 9) { puts("0"); continue; } for (int i = 1; i < n; ++i) { seq[i-1] = 88+seq[i]-seq[i-1]; // 将两者的差值计算出来 } seq[n-1] = 0; /* for (int i = 0; i < n; ++i) { printf("%d ", seq[i]); } puts(""); */ da(seq, sa, n, 200); calheight(seq, sa, n-1); /* for (int i = 1; i < n; ++i) { for (int j = sa[i]; j < n; ++j) { printf("%d ", seq[j]); } puts(""); } for (int i = 0; i < n; ++i) { printf("hight[%d] = %d ", i, height[i]); } */ solve(); } return 0; }
3)可重叠的K次最长重复子串:要求串中该子串至少出现K次,但是某一段字符串可以贡献于多个子串。做法是二分枚举长度,然后与第2种一样进行分组,不同的这里需要统计同一组的后缀个数是否大于等于K即可。
POJ-3261 Milk Patterns
题意:给定一系列的数字串,求出这其中出现次数不低于K次的最长的子串,题目保证有结果。
分析:由于题目中给定的数字集合较大因先对数字进行离散化处理。然后通过后缀数组求出sa和height数组后,二分枚举长度进行分组,统计一组内的后缀个数是否大于K。
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <map> using namespace std; const int N = 20005; int seq[N], sa[N], rank[N], height[N], val[N]; int wa[N], wb[N], ws[N], wv[N]; int n, K, cnt; map<int,int>mp; bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } bool check(int mid) { int tot = 0; for (int i = 2; i <= n; ++i) { // 使用sa数组信息则从1到n,记得排名第0位被末尾添加的0占用了 tot = 1; while (height[i] >= mid) ++i, ++tot; if (tot >= K) return true; } return false; } void solve() { int l = 1, r = n, ret; while (l <= r) { int mid = (l + r) >> 1; if (check(mid)) { l = mid + 1; ret = mid; } else { r = mid - 1; } // printf("mid = %d, l = %d, r = %d, ", mid, l, r); } printf("%d ", ret); } int main() { while (scanf("%d %d", &n, &K) != EOF) { mp.clear(), cnt = 0; for (int i = 0; i < n; ++i) { scanf("%d", &seq[i]); val[cnt++] = seq[i]; } sort(val, val + n); cnt = unique(val, val + n) - val; for (int i = 0; i < cnt; ++i) { mp[val[i]] = i + 1; } for (int i = 0; i < n; ++i) { seq[i] = mp[seq[i]]; } seq[n] = 0; da(seq, sa, n+1, cnt+1); calheight(seq, sa, n); /* for (int i = 1; i <= n; ++i) { for (int j = sa[i]; j < n; ++j) { printf("%d ", seq[j]); } puts(""); } for (int i = 0; i < n; ++i) { printf("height[%d] = %d ", i, height[i]); } */ solve(); } return 0; }
子串的个数:求出给定一个串中不相同子串的个数
1)不相同子串的个数:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。对于每个后缀在考虑其带来的前缀的时候考虑其重复的部分减去就行了,重复前缀的数量为height数组在该子串处的值,可以理解为所有后缀中与该后缀最相似的公共前缀部分不再予以统计。如果说还有其他串与该串有公共前缀,那么这个串一定会包含在与最相似的串的相同前缀中。总的来说是一个传递的问题,保证一个前缀被统计的一次就不再会被统计第二次。
SPOJ-694/705 Distinct Substrings / New Distinct Substrings
题意:给定长度为N的字符串,求出其中不相同子串的个数,两题只是给定的N的大小不一样。
分析:求出sa、height、rank数组后。一种方式是枚举排名,一种是枚举位置。代码选择是后一种。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 50005; char str[N]; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } int main() { int T; scanf("%d", &T); while (T--) { scanf("%s", str); int len = strlen(str); for (int i = 0; i < len; ++i) { seq[i] = str[i]; } seq[len] = 0; da(seq, sa, len+1, 128); calheight(seq, sa, len); int ret = 0; for (int i = 0; i < len; ++i) { ret += (len - i) - height[rank[i]]; } printf("%d ", ret); } return 0; }
2)字符串任意区间的子串个数:首先对整个字符串求一次sa[]以及height[],之后对于任意区间[L, R],遍历一遍sa[],只要起点在[L, R]内的后缀就需要进行统计,类似于1)中的方法,不过有一个地方要特别注意的就是全部的sa[]不一定就是区间内的sa[],这是因为区间内的后缀比较时有额外的长度限制。可以证明遍历的过程要遵循如下的规则:
后缀s1和后缀s2现在是两个待比较的后缀,s1在前,s2在后,其起点都在区间[L, R]内,并设两串在区间中的长度为 len1, len2, 其全局的最长公共前缀为 lcp。现考虑在遍历sa[]时,如何从全局sa[]得到正确的局部sa[]:
1: lcp < len1 && lcp < len2 时说明两个串在未结束时就比较出了大小,全局和局部的sa[]统一,因此可以放心令s2作为下一个字典序后缀;
2: lcp >= len1 && lcp >= len2 时说明在其中一个串结束时,两个串对应字符都是相等的,这时需要根据len1和len2关系来决定,如果len1>len2那么就不用更换了;
3: lcp >= len1 && lcp < len2 时说明在其中一个串结束时,两个串对应字符都是相等的,由于s2的长度比s1长,因此字典序肯定大,因此需要更换当前的后缀;
4: lcp < len1 && lcp >= len2 时说明在其中一个串结束时,两个串对应字符都是相等的,由于s1的长度比s2长,因此字典序肯定大,因此不需更换当前的后缀。
其中2和4条件可以合并,如果4成立,那么必定有 len1 > len2,因此可以简化这个判断这个过程:if (len1 > len2 && lcp >= len2) 则不更换 else 更换。
直接理解就是给结果带来误差的情况只会是某个被lcp完全包含的后缀被排在了后面,那么它的正确位置应该是在最前面,由于在后面匹配的其长度仍未整个局部后缀的长度,而在选择下一个字典序后缀时屏蔽掉这个后缀即可。
HDU-4622 Reincarnation
题意:给定一个字符串,询问[L, R]之间的子串数量。
分析:除了使用上述方法外,另一种方法是直接对任意区间求出一个hash值(矩阵),然后dp求解某一区间(子矩阵)不同子串的数量。在处理 rmq 时预处理出log函数大大加快了速度。
#include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <ctime> using namespace std; const int N = 2005; char str[N]; int len, seq[N]; int sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; int f[N][30]; int lg[N]; bool 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; 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 (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } void initrmq() { int LIM = (int)log2(1.0*N); for (int i = 1; i <= len; ++i) { f[i][0] = height[i]; } for (int j = 1; j <= LIM; ++j) { for (int i = 1; i+(1<<j)-1 <= len; ++i) { f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]); } } } int query(int l, int r) { int k = lg[r-l+1]; return min(f[l][k], f[r-(1<<k)+1][k]); } int cal(int l, int r) { int ret = (r-l+2)*(r-l+1)/2, last = -1; int lcp, a, b, alen, blen, k = r-l+1; for (int i = 1; i <= len && k; ++i) { if (sa[i] >= l && sa[i] <= r) { // 说明该后缀位于所选的区间内,且全局rank最靠前 k--; if (last != -1) { a = last, b = i; if (a > b) swap(a, b); lcp = query(a+1, b); alen = r-sa[last]+1, blen = r-sa[i]+1; /* if (lcp < alen && lcp < blen) {last = i;} else if (lcp >= alen && lcp >= blen) { if (blen > alen) last = i; } else if (lcp >= alen) {last = i;} */ if (alen > blen && lcp >= blen) {} else last = i; ret -= min(lcp, min(alen, blen)); } else last = i; } } return ret; } void solve() { int Q, l, r; scanf("%d", &Q); while (Q--) { scanf("%d %d", &l, &r); printf("%d ", cal(l-1, r-1)); // 字符串编号从0开始 } } int main() { lg[0] = -1; for (int i = 1; i < N; ++i) { lg[i] = lg[i>>1] + 1; } int T; scanf("%d", &T); while (T--) { scanf("%s", str); len = strlen(str); for (int i = 0; i < len; ++i) seq[i] = str[i]-'a'+1; seq[len] = 0; da(seq, sa, len+1, 27); calheight(seq, sa, len); initrmq(); solve(); } return 0; }
回文子串:字符串L的某个子字符R反过来写后和原先的字符串R一样
1)最长回文子串:将给定的字符串反序后添加到原串的末尾,中间用特殊字符隔开,设这个字符为$。设原串长度为N,枚举0-N的每一位作为中心(奇数回文串、偶数回文串中心偏左的字符),那么回文串的长度其实枚举位置开始的后缀和关于$对称位置后缀(奇数回文串、偶数回文串为对称位置向后移一位)的最长公共前缀。中间插入的字符不能够是添加到串尾的字符,其作用就是防止最长公共前缀超过一个串的原本的最长长度。
URAL-1297 Palindrome
题意:给定一个字符串,求其中的最长的回文串。
分析:解法如上所述,使用的树状数组来求区间最小值。其实使用Manacher算法能够很好解决该题。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 1005 << 1; // 牵涉到回文问题需要将原串反转附加到后面 int sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; char str[N]; int seq[N]; int bit[N]; int comlen; bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) { for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } } inline int lowbit(int x) { return x & (-x); } void modify(int p) { for (int i = p; i <= comlen; ++i) { bit[i] = height[i]; for (int j = 1; j < lowbit(i); j <<= 1) { bit[i] = min(bit[i], bit[i-j]); } } } int query(int L, int R) { int ret = height[R]; while (1) { ret = min(ret, height[R]); if (L == R) break; for (R -= 1; R-L >= lowbit(R); R -= lowbit(R)) { ret = min(ret, bit[R]); } } return ret; } void solve(int n) { int Max = 0, pos; modify(1); for (int i = 0; i < n; ++i) { int L = rank[i], R = rank[2*n-i+1]; //偶数回文情况 if (L > R) swap(L, R); int tmp = query(L+1, R); if (tmp*2 > Max) { Max = tmp*2, pos = i; } L = rank[i], R = rank[2*n-i]; // 奇数回文情况 if (L > R) swap(L, R); tmp = query(L+1, R); if (tmp*2-1 > Max) { Max = tmp*2-1, pos = i; } } for (int i = pos-Max/2; Max--; ++i) { putchar(seq[i]); } puts(""); } int main() { while (scanf("%s", str) != EOF) { int len = strlen(str); for (int i = 0; i < len; ++i) { seq[i] = seq[len*2-i] = str[i]; } seq[len] = 1, seq[len*2+1] = 0; comlen = len*2+1; da(seq, sa, comlen+1, 128); calheight(seq, sa, comlen); solve(len); } return 0; }
连续重复子串:如果一个字符串L是有某个字符串S重复R次而得到,则称L是一个连续重复串。R是这个字符串的重复次数
1)连续重复子串:给定一个串L,一直这个字符串是由某个字符串S重复R次而得到的,求R的最大值。求出sa和height数组后,枚举重复的串的长度K(K为L的约数),判定只需求suffix(1)和suffix(1+k)的最长公共前缀是否等于n-k,其实也就等价于KMP算法的含义了,计算任意一个后缀与第一个后缀的最长公共前缀。
POJ-2406 Power Strings
题意:给定一个字符串,已知其实一个字符串重复n次而来,求出最大的n。
分析:按照上面的解法,倍增算法TLE,DC3勉强能过。下面贴个KMP的代码。
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 1000005; char str[N]; int next[N]; void getnext(char str[], int n) { int i, j; next[0] = -1; for (i = 1, j = 0; i < n; ) { if (j == -1 || str[i] == str[j]) { ++i, ++j; next[i] = j; } else { j = next[j]; } } } int main() { while (scanf("%s", str), str[0] != '.') { int len = strlen(str); getnext(str, len); if (len % (len-next[len]) != 0) puts("1"); else printf("%d ", len / (len-next[len])); } return 0; }
2)重复次数最多的连续重复子串:给定一个字符串,求出重复次数最多的连续重复子串。解决这个问题的过程不像前面那些问题那么直接了。按步骤来,首先求出sa数组及height数组,接着枚举连续重复子串中不断重复那部分字符串的长度,称这个串为元串,长度为 i ;从1到max(len/2, 1),枚举的元串假设其重复次数至少为两次,那么其一定会跨过两个 i 的整数倍点,因为如果只跨过一个点的话,其最长长度为2*i-1,不满足重复至少两次的要求;对两个整数倍点 i*j, i*j+i 求一次lcp,得出的长度为该元串在 i*j 位置能够重复出现的最长长度L,将这个长度除以 i 加1将得到重复次数L/i+1。这还没完,因为经过这两个点的情况还不完备,应还可以假设起点在 [ i*j-i+1, i*j-d],其中 d = i-L/i 其意义为根据已知的匹配长度,可以将起点往前移动的范围,太靠后将不能够构造出比之前更好的解。如果要求出某个最多的连续重复子串的最小字典序子需要枚举所有起点,但如果只是要的到最多的重复次数或者任意最多的连续重复子串,那么只需要枚举i*j-d处的起点即可,因为后面的起点若能够得到最优的结果,那么i*j-d处也一定能得到,且答案一样均为L/i+2。
SPOJ-687 Repeats
题意:给定一个字符串,求出重复次数最多的连续重复子串的重复次数。
分析:直接求即可,每次只需要枚举除整数倍之外的一个起点。
#include <cstdlib> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> using namespace std; const int N = 50005; int n; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; int f[N][30]; inline bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } int rmqinit(int n) { int LIM = (int)log2(1.0*n); for (int i = 1; i <= n; ++i) { f[i][0] = height[i]; } for (int j = 1; j <= LIM; ++j) { for (int i = 1; i+(1<<j)-1 <= n; ++i) { f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]); } } } int query(int l, int r) { int k = (int)log2(1.0*(r-l+1)); return min(f[l][k], f[r-(1<<k)+1][k]); } void solve(int n) { int ret = 1; // ret 保留最长的重复次数 for (int i = 1; i <= n/2; ++i) { for (int j = 0; i*j+i < n; ++j) { // 枚举包含的起点 int l = rank[i*j], r = rank[i*j+i]; if (l > r) swap(l, r); int x = query(l + 1, r), y = 0; int delta = i - x % i; if (delta && j) { l = rank[i*j-delta], r = rank[i*j-delta+i]; if (l > r) swap(l, r); y = query(l + 1, r); } ret = max(ret, max(x/i+1, y/i+1)); } } printf("%d ", ret); } int main() { int T; char str[5]; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%s", str); seq[i] = str[0]-'a'+1; } seq[n] = 0; da(seq, sa, n+1, 3); calheight(seq, sa, n); rmqinit(n); solve(n); } return 0; }
POJ-3693 Maximum repetition substring
题意:给定一个字符串,求出重复次数最多的连续重复子串,优先重复次数最多,否则输出字典序最小的子串。
分析:与上题不同的是需要枚举多个起点,因此尽管答案相同,可能字典序较之要小。用到了string的一个赋值函数str.assign(const char *, int, int);
#include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <string> #include <algorithm> using namespace std; const int N = 100005; char str[N]; int seq[N]; int sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; int f[N][30]; inline bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) { for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } } void rmqinit(int n) { int LIM = (int)log2(1.0*n); for (int i = 1; i <= N; ++i) { f[i][0] = height[i]; // 相当于赋值一次原始数据,下面一个类似倍增思想的动态规划更新过程 } for (int j = 1; j <= LIM; ++j) { for (int i = 1; i+(1<<j)-1 <= N; ++i) { f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]); // 算术运算符优先级中*/+-均高于左右移运算符 } } } int query(int l, int r) { int k = (int)log2(1.0*(r-l+1)); return min(f[l][k], f[r-(1<<k)+1][k]); } string solve(int len) { int Max = 0, lim = max(len/2, 1); string tmp, ans; for (int i = 1; i <= lim; ++i) { // 枚举重复子串重复部分的长度 for (int j = 0; i*j+i < len; ++j) { // 枚举i长度的倍数节点 int l = rank[i*j], r = rank[i*j+i]; if (l > r) swap(l, r); int x = query(l + 1, r); if (x/i+1 >= Max) { tmp.assign(str, i*j, i*(x/i+1)); if (x/i+1 == Max) { if (ans > tmp || !ans.length()) ans = tmp; } else { Max = x/i+1; ans = tmp; } } int delta = i - x % i; if (delta && j) { for (int k = delta; k < i; ++k) { // 这个区间都可能是最长重复子串 l = rank[i*j-k], r = rank[i*j+i-k]; if (l > r) swap(l, r); int y = query(l + 1, r); if (y/i+1 >= Max) { tmp.assign(str, i*j-k, i*(y/i+1)); if (Max == y/i+1) { if (ans > tmp || !ans.length()) ans = tmp; } else { Max = y/i+1; ans = tmp; } } else break; } } } } return ans; } int main() { int ca = 0; while (scanf("%s", str), str[0] != '#') { int len = strlen(str); for (int i = 0; i < len; ++i) { seq[i] = str[i]-'a'+1; } seq[len] = 0; da(seq, sa, len+1, 27); calheight(seq, sa, len); rmqinit(len); printf("Case %d: %s ", ++ca, solve(len).c_str()); } return 0; }
公共子串:如果字符串L同时出现在字符串A和字符串B中,则称字符串L是字符串A和字符串B的公共子串
1)最长公共子串:给定两个字符串A和B,求最长公共子串。首先将两个字符串合并成一个字符串,通过二分枚举长度,然后在height分组中查看是否存在在两个不同串的后缀即可。也可以证明最长的公共子串所在后缀是相邻的,因此可以直接遍历一遍height数组,判定相邻的两个height数组是否属于不同的两个串。
POJ-2774 Long Long Message
题意:给定两个串,求出最长的公共子串的长度。
分析:使用上述的方法即可。
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; const int N = 100005<<1; char s1[N>>1], s2[N>>1]; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; inline bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } bool check(int mid, int n, int len1) { for (int i = 2; i <= n; ++i) { int Min = sa[i-1], Max = sa[i-1]; while (height[i] >= mid) { Min = min(Min, sa[i]); Max = max(Max, sa[i]); ++i; } if (Min < len1 && Max > len1) return true; } return false; } int solve(int len, int len1, int len2) { int ret = 0, l = 1, r = min(len1, len2); while (l <= r) { int mid = (l + r) >> 1; if (check(mid, len, len1)) { ret = mid; l = mid + 1; } else { r = mid - 1; } } return ret; } int main() { while (scanf("%s %s", s1, s2) != EOF) { int len1 = strlen(s1); int len2 = strlen(s2); for (int i = 0; i < len1; ++i) { seq[i] = s1[i]-'a'+2; } seq[len1] = 1; for (int i = 0; i < len2; ++i) { seq[len1+i+1] = s2[i]-'a'+2; } seq[len1+len2+1] = 0; int len = len1+len2+1; da(seq, sa, len+1, 30); calheight(seq, sa, len); printf("%d ", solve(len, len1, len2)); } return 0; }
URAL-1517 Freedom of Choice
题意:较之上题,该题要求输出任意一个最长的公共子串,求解的时候保留一下子串的位置信息即可。
#include <cstdio> #include <cstring> #include <cstdlib> #include <string> #include <algorithm> using namespace std; const int N = 100005<<1; char s1[N>>1], s2[N>>1]; int n; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; inline bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } bool check(int mid, int len, string &tmp) { for (int i = 2; i <= len; ++i) { int Min = sa[i-1], Max = sa[i-1]; while (height[i] >= mid) { Min = min(Min, sa[i]); Max = max(Max, sa[i]); ++i; } if (Min < n && Max > n) { tmp.assign(s1, Min, mid); return true; } } return false; } void solve(int len, int n) { int ret = 0, l = 1, r = n; string tmp; while (l <= r) { int mid = (l + r) >> 1; if (check(mid, len, tmp)) { ret = mid; l = mid + 1; } else { r = mid - 1; } } puts(tmp.c_str()); } int main() { while (scanf("%d", &n) != EOF) { scanf("%s %s", s1, s2); for (int i = 0; i < n; ++i) { seq[i] = s1[i]-'A'+2; } seq[n] = 1; for (int i = 0; i < n; ++i) { seq[n+i+1] = s2[i]-'A'+2; } seq[n*2+1] = 0; int len = n*2+1; da(seq, sa, len+1, 30); calheight(seq, sa, len); solve(len, n); } return 0; }
2)公共子串的个数:给定两个字符串A和B,求长度不小于K的公共子串的个数。将串B添加到A之后,中间使用一个特殊字符分隔开(为了防止两个后缀串的公共前缀跨越两个字符串),求出sa数组和height数组后,扫描一遍height数组,并且进行分组,分组的时候要维护一个height值单调上升的栈,栈中的每一个元素拥有两个属性,第一个是其值为多少,第二个是前面还有多少个能够提供这个值的一共有多少个(如果新加入的height值比之前较小时,将回收之前的height值,将其视为同一高度,直到遇到比它小的)。需要对height数组作两次。
POJ-3415 Common Substrings
题意:求两个串的公共子串的个数。
分析:如上所述。
#include <cstdlib> #include <cstring> #include <cstdio> #include <stack> #include <algorithm> using namespace std; typedef long long LL; const int N = 100005<<1; const int inf = 0x3f3f3f3f; int K, len1, len2, len; char s1[N>>1], s2[N>>1]; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; inline bool 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; 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 (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } int s[N][2]; void solve() { LL tot = 0, top = 0, sum = 0; for (int i = 1; i <= len; ++i) { if (height[i] < K) top = tot = 0; // 维护一个单调上升的栈 else { int cnt = 0; if (sa[i-1] < len1) cnt++, tot += height[i]-K+1; // 如果前一个串是A串,那么将这个串加入到带匹配A串中 while (top > 0 && height[i] <= s[top-1][0]) { top--; tot -= s[top][1]*(s[top][0]-height[i]); cnt += s[top][1]; // s[top][1]伴随s[top][0]而用来计数的变量 } s[top][0] = height[i]; // 保留高度值 s[top++][1] = cnt; // 保留该处能够匹配到的串的个数 if (sa[i] > len1) sum += tot; // B串去匹配A串 } } tot = top = 0; for (int i = 1; i <= len; ++i) { if (height[i] < K) top = tot = 0; else { int cnt = 0; if (sa[i-1] > len1) cnt++, tot += height[i]-K+1; while (top > 0 && height[i] <= s[top-1][0]) { top--; tot -= s[top][1]*(s[top][0]-height[i]); cnt += s[top][1]; } s[top][0] = height[i]; s[top++][1] = cnt; if (sa[i] < len1) sum += tot; // A串去匹配B串 } } printf("%I64d ", sum); } int main() { while (scanf("%d", &K), K) { scanf("%s %s", s1, s2); len1 = strlen(s1), len2 = strlen(s2); len = len1+len2+1; for (int i = 0; i < len1; ++i) seq[i] = s1[i]; for (int i = 0; i < len2; ++i) { seq[len1+i+1] = s2[i]; } seq[len1] = 1, seq[len] = 0; da(seq, sa, len+1, 128); calheight(seq, sa, len); solve(); } return 0; }
多个字符串的相关问题
1)不小于K个字符串中的最长子串:同时出现在不少于K个串的子串。做法同样二分枚举长度,分组后在一组中寻找出现字符串是否超过K即可。
POJ-3294 Life Forms
题意:给定N个串,求超过一半串拥有的最长子串。
分析:将所有的串连接起来后二分枚举长度分组。
#include <cstdlib> #include <cstring> #include <cstdio> #include <stack> #include <algorithm> #include <vector> #include <string> using namespace std; const int N = 1005*100; int m; char str[1005]; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; int vis[105], ll[105]; vector<pair<int,int> >v; inline bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } int getid(int x) { int i; for (i = 0; i < m; ++i) { if (x >= (ll[i]+1)) x -= ll[i]+1; else break; } return i; } bool check(int mid, int len) { int tot; vector<pair<int, int> >vt; for (int i = 2; i <= len; ++i) { memset(vis, 0, sizeof (vis)); tot = 1; vis[getid(sa[i-1])] = 1; while (height[i] >= mid) { if (!vis[getid(sa[i])]) { vis[getid(sa[i])] = 1; ++tot; } ++i; } if (tot * 2 > m) { vt.push_back(make_pair(sa[i-1], mid)); } } if (vt.size()) { v.clear(); for (int i = 0; i < (int)vt.size(); ++i) { v.push_back(vt[i]); } } return vt.size(); } void solve(int len, int Min) { int l = 1, r = Min; v.clear(); while (l <= r) { int mid = l + r >> 1; if (check(mid, len)) { l = mid + 1; } else { r = mid - 1; } } if (!v.size()) { puts("?"); return; } for (int i = 0; i < (int)v.size(); ++i) { for (int k = 0, j = v[i].first; k < v[i].second; ++k, ++j) { putchar(seq[j]); } puts(""); } } int main() { int first = true; while (scanf("%d", &m), m) { if (first) first = false; else puts(""); int len = 0, Min = 1005; for (int i = 0; i < m; ++i) { scanf("%s", str); Min = min(Min, ll[i] = strlen(str)); for (int j = 0; j < ll[i]; ++j) { seq[len++] = str[j]; } seq[len++] = 200+i; } seq[len] = 0; da(seq, sa, len+1, 305); calheight(seq, sa, len); solve(len, Min); } return 0; }
2)每个字符串至少出现两次且不可重叠的最长子串:二分枚举长度后在同一分组中对每一个字符串保留一个最小的位置和一个最大的位置,最后查看是否每个串在同一组中都有至少两个后缀,并且后缀的坐标差大于枚举的长度。
SPOJ-220 Relevant Phrases of Annihilation
题意:给定N个串,求每个串至少出现两次的最长子串。
分析:如上所述。
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int inf = 0x3f3f3f3f; const int N = 10005*10; char str[10005]; int seq[N], sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; int m, ll[15]; int cnt[15], pos[15][2]; inline bool 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; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 0; 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 = 0; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), 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++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } int getid(int x) { int i; for (i = 0; i < m; ++i) { if (x >= (ll[i]+1)) x -= ll[i]+1; else break; } return i; } bool check(int mid, int len) { int k; for (int i = 2; i <= len; ++i) { memset(cnt, 0, sizeof (cnt)); cnt[getid(sa[i-1])]++; pos[getid(sa[i-1])][0] = pos[getid(sa[i-1])][1] = sa[i-1]; while (height[i] >= mid) { int id = getid(sa[i]); if (cnt[id] == 0) { cnt[id] = 1, pos[id][0] = pos[id][1] = sa[i]; } else { cnt[id] = 2; pos[id][0] = min(pos[id][0], sa[i]); pos[id][1] = max(pos[id][1], sa[i]); } ++i; } for (k = 0; k < m; ++k) { if (cnt[k] < 2 || pos[k][1]-pos[k][0] < mid) break; } if (k == m) return true; } return false; } void solve(int len, int Min) { int ret = 0, l = 1, r = Min/2; while (l <= r) { int mid = (l + r) >> 1; if (check(mid, len)) { ret = mid; l = mid + 1; } else r = mid - 1; } printf("%d ", ret); } int main() { int T, len, Min; scanf("%d", &T); while (T--) { len = 0, Min = 10005; scanf("%d", &m); for (int i = 0; i < m; ++i) { scanf("%s", str); Min = min(Min, ll[i] = strlen(str)); for (int j = 0; j < ll[i]; ++j) { seq[len++] = str[j]; } seq[len++] = 200+i; } seq[len] = 0; da(seq, sa, len+1, 300); calheight(seq, sa, len); solve(len, Min); } return 0; }