后綴數組是解決一系列字符串題目的利器,后綴數組中保留了這樣的信息。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\n", 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\n", 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, \n", mid, l, r); } printf("%d\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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\n", ++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\n", 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\n", 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\n", 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; }