BZOJ 3230 相似子串 后缀数组
2016-03-24 09:04
387 查看
之前觉得后缀数组谜一样的豆腐块代码很难理解,于是先去学了后缀自动机,然而这道题让我发现后缀数组还是很好用的工具QaQ。
关于这道题的每个询问首先要求出第i小和第j小的子串在原串的位置,然后快速地求出这两个子串的最长公共前缀和后缀。
第一个步骤我们可以用二分搞定,通过原串的height数组我们可以求出一个sum数组,sum[i]表示按照字典序排序后,前i个后缀中共有多少个本质不同的子串。在n个后缀中,每个子串都可以被表示为某个后缀的前缀,所以我们在sum数组中二分查找出第一个不小于k的sum[i]来求原串第k小的子串。那么第i个后缀的某个前缀就是所需的子串了,这个稍微计算一下得出。
我们得出了两个子串[la,ra]和[lb,rb],要求它们的最长公共前缀以及后缀,最长公共前缀就是height数组上的区间最小值问题了,那么后缀我们按照原串的反串建立后缀数组也就变成了height数组上的区间最小值问题了。
【蒟蒻的自述】
然而蒟蒻不理解后缀数组的豆腐块代码,所以一开始出了好多错误。
在前一部分计算sa数组的时候,为了防止出错所以在原串后加入一个原串没有的字符,并且它小于任何一个原串内的字符,所以传入的n实际是原串长度+1,并且原串s转换后最小字符不能是0,因为被加在末尾的字符是最小的。这也导致sa数组的有效位置是[1,n],而rank仍然是[0,n-1],不过我们仍然可以按照字面意思来理解它们,sa[i]表示排名第i的后缀的开始位置,rank[i]表示开始位置为i的后缀的排名,height[i]表示排名第i的后缀与排名第i-1的后缀的LCP。
关于这道题的每个询问首先要求出第i小和第j小的子串在原串的位置,然后快速地求出这两个子串的最长公共前缀和后缀。
第一个步骤我们可以用二分搞定,通过原串的height数组我们可以求出一个sum数组,sum[i]表示按照字典序排序后,前i个后缀中共有多少个本质不同的子串。在n个后缀中,每个子串都可以被表示为某个后缀的前缀,所以我们在sum数组中二分查找出第一个不小于k的sum[i]来求原串第k小的子串。那么第i个后缀的某个前缀就是所需的子串了,这个稍微计算一下得出。
我们得出了两个子串[la,ra]和[lb,rb],要求它们的最长公共前缀以及后缀,最长公共前缀就是height数组上的区间最小值问题了,那么后缀我们按照原串的反串建立后缀数组也就变成了height数组上的区间最小值问题了。
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #define SZ 100005 using namespace std; int n, q, s1[SZ], s2[SZ]; int sa1[SZ], r1[SZ], h1[SZ], rmq1[SZ][18]; int sa2[SZ], r2[SZ], h2[SZ], rmq2[SZ][18]; long long sum[SZ]; void Read () { scanf ("%d %d", &n, &q); char c = getchar(); while (c < 'a' || c > 'z') c = getchar(); for (int i = 1; i <= n; i++) { s1[i-1] = s2[n-i] = c-'a'+1; c = getchar(); } } int cmp (int *s, int i, int j, int l) {return s[i]==s[j] && s[i+l]==s[j+l];} void buildsa (int *s, int *sa, int *rank, int *height, int m) { n++; int a[SZ], b[SZ], c[SZ], d[SZ]; int i, j, p, k = 0, *x = a, *y = b, *t; for (i = 0; i < m; i++) c[i] = 0; for (i = 0; i < n; i++) c[x[i]=s[i]]++; for (i = 1; i < m; i++) c[i] += c[i-1]; for (i = n-1; i >= 0; i--) sa[--c[x[i]]] = i; for (j = p = 1; p < n; j<<=1, 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++) d[i] = x[y[i]]; for (i = 0; i < m; i++) c[i] = 0; for (i = 0; i < n; i++) c[d[i]]++; for (i = 1; i < m; i++) c[i] += c[i-1]; for (i = n-1; i >= 0; i--) sa[--c[d[i]]] = y[i]; for (t=x,x=y,y=t,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++; } n--; 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]; s[i+k]==s[j+k]; k++); } void BuildSa () { buildsa(s1,sa1,r1,h1,27); buildsa(s2,sa2,r2,h2,27); sum[1] = n-sa1[1]; for (int i = 2; i <= n; i++) sum[i] = sum[i-1]+(long long)(n-sa1[i]-h1[i]); } void BuildRmq () { for (int i = 1; i <= n; i++) rmq1[i][0] = h1[i], rmq2[i][0] = h2[i]; for (int i = 1; (1<<i) <= n; i++) for (int j = 1; j+(1<<i)-1 <= n; j++) rmq1[j][i] = min(rmq1[j][i-1],rmq1[j+(1<<i-1)][i-1]), rmq2[j][i] = min(rmq2[j][i-1],rmq2[j+(1<<i-1)][i-1]); } void Query (long long k, int &lk, int &rk) { if (sum < k) {lk = -1; return;} int l = 1, r = n, mid; while (l < r) { mid = (l+r)/2; if (sum[mid] >= k) r = mid; else l = mid + 1; } lk = sa1[l]; rk = sa1[l]+k+h1[l]-(l?sum[l-1]:0)-1; } int lcp (int l, int r, bool flag) { if (l == r) return 9999999; if (flag) { l = r1[l], r= r1[r]; if (l > r) swap(l,r); int k = 0; l++; if (l == r) return rmq1[l][0]; while ((1<<k+1) <= r-l+1) k++; return min(rmq1[l][k],rmq1[r-(1<<k)+1][k]); } else { l = r2[l], r= r2[r]; if (l > r) swap(l,r); int k = 0; l++; if (l == r) return rmq2[l][0]; while ((1<<k+1) <= r-l+1) k++; return min(rmq2[l][k],rmq2[r-(1<<k)+1][k]); } } void SolveQue () { #define SKIP_THIS puts("-1");continue; long long a, b, ansa, ansb, mxans; int la, lb, ra, rb; while (q--) { scanf ("%lld %lld", &a, &b); Query (a, la, ra); Query (b, lb, rb); if (la < 0 || lb < 0) {SKIP_THIS} mxans = (long long)(min(ra-la,rb-lb) + 1); ansa = min((long long)lcp(la,lb,1), mxans); ansb = min((long long)lcp(n-ra-1,n-rb-1,0), mxans); printf ("%lld\n", ansa*ansa+ansb*ansb); } } int main () { Read(); BuildSa(); BuildRmq(); SolveQue(); return 0; }
【蒟蒻的自述】
然而蒟蒻不理解后缀数组的豆腐块代码,所以一开始出了好多错误。
在前一部分计算sa数组的时候,为了防止出错所以在原串后加入一个原串没有的字符,并且它小于任何一个原串内的字符,所以传入的n实际是原串长度+1,并且原串s转换后最小字符不能是0,因为被加在末尾的字符是最小的。这也导致sa数组的有效位置是[1,n],而rank仍然是[0,n-1],不过我们仍然可以按照字面意思来理解它们,sa[i]表示排名第i的后缀的开始位置,rank[i]表示开始位置为i的后缀的排名,height[i]表示排名第i的后缀与排名第i-1的后缀的LCP。
相关文章推荐
- 剑指 offer代码解析——面试题34丑数
- 如何面试架构师
- 剑指 offer代码解析——面试题34丑数
- c++作业2
- apache maven 基本知识
- 第四周项目训练3
- 第四周【递归函数fib】
- ASP.NET-Router配置中MapRoute的参数
- java打包jar类库
- PowerDesigner远程连接oracle11g数据库,导入表,增加表注释
- 实体
- 第3周项目10-谁是小偷
- IP设置同一网段
- 企业级开发:Gitflow Workflow工作流
- 猜数字
- "The connection to adb is down, and a severe error has occured."亲测有用的方法
- 很反感Java Web 三层框架
- 【HTML5】H5新标签大实例
- 小试循环(倒数和)
- Java内存模型FAQ(一)什么是内存模型