您的位置:首页 > 其它

poj 3415 :长度不小于 k 的公共子串的个数(后缀数组+单调栈)

2016-09-02 10:29 591 查看
题目:http://poj.org/problem?id=3415

题意:

给定两个字符串 A 和 B,求长度不小于 k 的公共子串的个数(可以相同)。 样例 1: A=“xx”,B=“xx”,k=1,长度不小于 k 的公共子串的个数是 5。 样例 2: A=“aababaa”,B=“abaabaa”,k=2,长度不小于 k 的公共子串的个数是22。

分析:

基本思路是计算 A 的所有后缀和 B 的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于 k 的部分全部加起来。先将两个字符串连起来,中间用一个没有出现过的字符隔开。按 height 值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。扫描一遍,每遇到一个 B 的后缀就统计与前面的 A 的后缀能产生多少个长度不小于 k 的公共子串,这里 A 的后缀需要用一个单调的栈来高效的维护。然后对 A 也这样做一次。

这题比较麻烦的是维护单调栈,一个比较简单的方法是栈中保存height值,维护height值单调递增,那么对于新加入的height值,sum保存前i个构成的公共子串数,如果height[i]<栈顶height值,说明(he[top] - height[i])这部分是多加上的,应该减去。因为对于后缀j和i,二者的最长公共前缀是min{height[rank[j]+1],height[rank[j]+1]..height[rank[i]] }

看到网上还有个不错的写法:http://blog.csdn.net/xingyeyongheng/article/details/16919251

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 9;
const int N = 2e5 + 9;

/********************倍增算法*后缀数组模板*******************************/

int sa
, t1
, t2
, c
, rk
, height
;
void build_sa (int s[], int n, int m) {
int i, k, p, *x = t1, *y = t2;
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 (k = 1; k <= n; k <<= 1) {
p = 0;
for (i = n - k; i < n; i++) y[p++] = i;
for (i = 0; i < n; i++) if (sa[i] >= k) y[p++] = sa[i] - k;
for (i = 0; i < m; i++) c[i] = 0;
for (i = 0; i < n; i++) c[x[y[i]]]++;
for (i = 1; i < m; i++) c[i] += c[i - 1];
for (i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap (x, y);
p = 1;
x[sa[0]] = 0;
for (i = 1; i < n; i++)
x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p ++;
if (p >= n) break;
m = p;
}
}
void getHeight (int s[], int n) {
int i, j, k = 0;
for (i = 0; i <= n; i++) rk[sa[i]] = i;
for (i = 0; i < n; i++) {
if (k) k--;
j = sa[rk[i] - 1];
while (s[i + k] == s[j + k]) k++;
height[rk[i]] = k;
}
}
/********************************************************************************/

ll solve (int n, int len, int k) {
int *cnt = t1, *he = t2, top = 0;
//cnt保存当前栈he中每个height值的个数;he是栈,保存已经扫过的height值,当中元素时单调递增的,top栈顶指针
ll sum = 0, ans = 0;
for (int i = 1; i <= n; i++) {
if (height[i] < k) {
sum = top = 0;
continue;
}
int tot = 0;
if (sa[i - 1] < len) { //如果sa[i-1]是A串,那么sum加上height[i]-k+1个sa[i]和sa[i-1]构成的公共子串个数
tot++;
sum += height[i] - k + 1;
}
while (top > 0 && height[i] <= he[top - 1]) {
top--;
sum -= cnt[top] * (he[top] - height[i]); //因为栈顶的height值大于height[i],那么当前sa[i]跟之前的sa[j]就多加了
//cnt[top]*(he[top]-height[i])个子串,所以要减去(因为这些更长的后缀部分已经不能跟后面的后缀形成子串了。
tot += cnt[top];
}
if (tot) {
he[top] = height[i];
cnt[top++] = tot;
}
if (sa[i] > len) ans += sum;
}
for (int i = 1; i <= n; i++) {
if (height[i] < k) {
sum = top = 0;
continue;
}
int tot = 0;
if (sa[i - 1] > len) {
tot++;
sum += height[i] - k + 1;
}
while (top > 0 && height[i] <= he[top - 1]) {
top--;
sum -= cnt[top] * (he[top] - height[i]);
tot += cnt[top];
}
he[top] = height[i];
cnt[top++] = tot;
if (sa[i] < len) ans += sum;
}
return ans;

}
int s
, n, a
, cas = 0, k;
char str
;
int main() {
//freopen ("f.txt", "r", stdin);

while (~scanf ("%d", &k) && k ) {
scanf ("%s", str);
int len = strlen (str);
for (int i = 0; i < len; i++) s[i] = str[i];
s[len] = '#';
scanf ("%s", str + len + 1);
int n = len;
for (++n; str
!= '\0'; n++) s
= str
;
s
= 0;
build_sa (s, n + 1, 128);
getHeight (s, n);
cout << solve (n, len, k) << endl;
}
return 0;
}
/*
Sample Input

2
aababaa
abaabaa
1
xx
xx
0

Sample Output

22
5

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐