其实是一个很经典的字符串问题,但是我们比赛的时候没出。
先看一下UVA11107这题,题意是,找出最长的一个字符串,在至少一半的字符串中出现过。只要把所有的字符串用不同的分隔符分开,然后SA一下,最后二分长度,用height将字符串分组,判断是否超过一半即可。要注意的是,因为分隔符单单用个char已经不够了,所以全部char都换成int,然后用不同的整数来作为分隔符即可。
代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <vector> 5 using namespace std; 6 const int N = 100000 + 1000; 7 typedef long long ll; 8 const int sep = 'z' + 1; 9 10 /** 11 * sa[i]:表示排在第i位的后缀的起始下标 12 * rank[i]:表示后缀suffix(i)排在第几 13 * height[i]:sa[i-1] 与 sa[i]的LCP(最长公共前缀)值 14 * 15 * */ 16 /* 17 如果整数的话模板改成int. 18 加一个数a[n] = 0 。 这样他的排名是第一个。 19 construct(a,n+1); 20 21 字符串的话。 22 len = strlen(str); 23 construct(s,strlen(s)+1); 24 排名第0的是个空字符串。 25 26 height[i]:sa[i-1] 与 sa[i]的LCP(最长公共前缀)值 27 所以height[1] = 0; 28 rank[len] = 0; 29 sa[0] = len; 30 */ 31 int sa[N],rnk[N],height[N]; 32 void construct(const int *s,int n,int m = 256) { 33 static int t1[N],t2[N],c[N]; 34 int *x = t1,*y = t2; 35 int i,j,k,p,l; 36 for (i = 0; i < m; ++ i) c[i] = 0; 37 for (i = 0; i < n; ++ i) c[x[i] = s[i]] ++; 38 for (i = 1; i < m; ++ i) c[i] += c[i - 1]; 39 for (i = n - 1; i >= 0; -- i) sa[--c[x[i]]] = i; 40 for (k = 1; k <= n; k <<= 1) { 41 p = 0; 42 for (i = n - k; i < n; ++ i) y[p++] = i; 43 for (i = 0; i < n; ++ i) if (sa[i] >= k) y[p++] = sa[i] - k; 44 for (i = 0; i < m; ++ i) c[i] = 0; 45 for (i = 0; i < n; ++ i) c[x[y[i]]] ++; 46 for (i = 1; i < m; ++ i) c[i] += c[i - 1]; 47 for (i = n - 1; i >= 0; -- i) sa[--c[x[y[i]]]] = y[i]; 48 std::swap(x,y); 49 p = 1; x[sa[0]] = 0; 50 for (i = 1; i < n; ++ i) 51 x[sa[i]] = y[sa[i - 1]] == y[sa[i]] 52 && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1: p ++; 53 if (p >= n) break; 54 m = p; 55 } 56 for (i = 0; i < n; ++ i) rnk[sa[i]] = i; 57 for (i = 0,l = 0; i < n; ++ i) { 58 if (rnk[i]) { 59 j = sa[rnk[i] - 1]; 60 while (s[i + l] == s[j + l]) l++; 61 height[rnk[i]] = l; 62 if (l) l--; 63 } 64 } 65 } 66 67 char str[1005]; 68 int s[N]; 69 int End[105],len,n; 70 bool vis[105]; 71 vector<int> ans; 72 73 bool solve(int Len) 74 { 75 ans.clear(); 76 int cnt = 0; 77 memset(vis,false,sizeof(vis)); 78 for(int i=2;i<=len;i++) 79 { 80 if(height[i] >= Len) 81 { 82 for(int j=1;j<=n;j++) 83 { 84 if(sa[i] > End[j-1] && sa[i] < End[j]) {cnt += !vis[j]; vis[j] = 1;} 85 if(sa[i-1] > End[j-1] && sa[i-1] < End[j]) {cnt += !vis[j]; vis[j] = 1;} 86 } 87 } 88 else 89 { 90 if(cnt > n / 2) ans.push_back(sa[i-1]); 91 cnt = 0; 92 memset(vis,false,sizeof(vis)); 93 } 94 } 95 if(cnt > n / 2) ans.push_back(sa[len]); 96 return ans.size(); 97 } 98 99 int main() 100 { 101 int first = 0; 102 while(scanf("%d",&n) == 1 && n) 103 { 104 if(first == 0) first = 1; 105 else puts(""); 106 len = 0; 107 for(int i=1;i<=n;i++) 108 { 109 scanf("%s",str); 110 for(int j=0;str[j];j++) s[len++] = str[j]; 111 112 s[len++] = sep + i; 113 End[i] = len-1; 114 } 115 s[len] = 0; 116 construct(s,len+1); 117 int l = 1, r = len, Ans = -1; 118 while(l <= r) 119 { 120 int mid = l + r >> 1; 121 if(solve(mid)) l = mid + 1, Ans = mid; 122 else r = mid - 1; 123 } 124 125 if(Ans == -1) puts("?"); 126 else 127 { 128 // 为了得到答案,再solve一遍 129 solve(Ans); 130 for(int i=0;i<ans.size();i++) 131 { 132 for(int j=ans[i],cnt=1;cnt<=Ans;j++,cnt++) putchar(s[j]); 133 puts(""); 134 } 135 //puts(""); 136 } 137 } 138 return 0; 139 }
然后看下F题,题意是,找出最短的一个字符串,只在第一个字符串中出现。那么,一样的套路:全部连接,然后SA一遍,然后分组,找这一组中是不是都只是在第一个字符串中出现(这里采用了一个belong数组来判断每个字符是属于哪个串中的)。
代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <vector> 5 using namespace std; 6 const int N = 3e6+7; 7 const int M = 5e4+7; 8 typedef long long ll; 9 const int sep = 'z' + 1; 10 11 /** 12 * sa[i]:表示排在第i位的后缀的起始下标 13 * rank[i]:表示后缀suffix(i)排在第几 14 * height[i]:sa[i-1] 与 sa[i]的LCP(最长公共前缀)值 15 * 16 * */ 17 /* 18 如果整数的话模板改成int. 19 加一个数a[n] = 0 。 这样他的排名是第一个。 20 construct(a,n+1); 21 22 字符串的话。 23 len = strlen(str); 24 construct(s,strlen(s)+1); 25 排名第0的是个空字符串。 26 27 height[i]:sa[i-1] 与 sa[i]的LCP(最长公共前缀)值 28 所以height[1] = 0; 29 rank[len] = 0; 30 sa[0] = len; 31 */ 32 int sa[N],rnk[N],height[N]; 33 void construct(const int *s,int n,int m = 256) { 34 static int t1[N],t2[N],c[N]; 35 int *x = t1,*y = t2; 36 int i,j,k,p,l; 37 for (i = 0; i < m; ++ i) c[i] = 0; 38 for (i = 0; i < n; ++ i) c[x[i] = s[i]] ++; 39 for (i = 1; i < m; ++ i) c[i] += c[i - 1]; 40 for (i = n - 1; i >= 0; -- i) sa[--c[x[i]]] = i; 41 for (k = 1; k <= n; k <<= 1) { 42 p = 0; 43 for (i = n - k; i < n; ++ i) y[p++] = i; 44 for (i = 0; i < n; ++ i) if (sa[i] >= k) y[p++] = sa[i] - k; 45 for (i = 0; i < m; ++ i) c[i] = 0; 46 for (i = 0; i < n; ++ i) c[x[y[i]]] ++; 47 for (i = 1; i < m; ++ i) c[i] += c[i - 1]; 48 for (i = n - 1; i >= 0; -- i) sa[--c[x[y[i]]]] = y[i]; 49 std::swap(x,y); 50 p = 1; x[sa[0]] = 0; 51 for (i = 1; i < n; ++ i) 52 x[sa[i]] = y[sa[i - 1]] == y[sa[i]] 53 && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1: p ++; 54 if (p >= n) break; 55 m = p; 56 } 57 for (i = 0; i < n; ++ i) rnk[sa[i]] = i; 58 for (i = 0,l = 0; i < n; ++ i) { 59 if (rnk[i]) { 60 j = sa[rnk[i] - 1]; 61 while (s[i + l] == s[j + l]) l++; 62 height[rnk[i]] = l; 63 if (l) l--; 64 } 65 } 66 } 67 68 char str[N]; 69 int s[N]; 70 int endlen[N],len,n,belong[N]; 71 char temp[N]; 72 int all; 73 int from; 74 75 bool check(int st,int ed,int L) 76 { 77 int now = -1; 78 for(int i=st;i<=ed;i++) 79 { 80 if(belong[sa[i]] == 1 && endlen[sa[i]] >= L) 81 { 82 if(now == -1) now = sa[i]; 83 continue; 84 } 85 return false; 86 } 87 from = now; 88 return true; 89 } 90 91 bool solve(int L) 92 { 93 int st = 1, ed = 1; 94 for(int i=2;i<all;i++) 95 { 96 if(height[i] >= L) ed++; 97 else 98 { 99 if(check(st,ed,L)) return 1; 100 st = ed = i; 101 } 102 } 103 return check(st,ed,L); 104 } 105 106 int main() 107 { 108 int T, kase = 1; 109 scanf("%d",&T); 110 while(T--) 111 { 112 printf("Case #%d: ",kase++); 113 scanf("%d%s",&n,temp); 114 int now_len = strlen(temp); 115 len = 0; 116 for(int i=0;temp[i];i++) 117 { 118 s[len] = temp[i]; 119 belong[len] = 1; 120 endlen[len] = now_len - i; 121 len ++; 122 } 123 s[len] = sep + 1; belong[len] = 1, endlen[len] = 0; 124 int first_len = len ++; 125 for(int i=2;i<=n;i++) 126 { 127 scanf("%s",str); 128 int now_len = strlen(str); 129 for(int j=0;j<now_len;j++) 130 { 131 s[len] = str[j]; 132 belong[len] = i; 133 endlen[len] = now_len - j; 134 len ++; 135 } 136 s[len] = sep + i; belong[len] = i, endlen[len] = 0; len++; 137 } 138 s[len] = 0; 139 construct(s,len+1,50000 + 500); 140 all = rnk[first_len]; 141 int l = 0, r = first_len, ans = -1; 142 while(l <= r) 143 { 144 int mid = l + r >> 1; 145 if(solve(mid)) r = mid - 1, ans = mid; 146 else l = mid + 1; 147 } 148 if(ans == -1) puts("Impossible"); 149 else 150 { 151 for(int i=from;i<from+ans;i++) printf("%c",temp[i]); 152 puts(""); 153 } 154 } 155 return 0; 156 }
感觉二分+SA就是一种套路,可以结合前几天做的那题一起看看,最少出现m次的最长字符串。