POJ3415--Common Substrings(后缀数组+单调栈优化)

Description

A substring of a string T is defined as:

T( ik)= TiTi +1... Ti+k -1, 1≤ ii+k-1≤| T|.

Given two strings AB and one integer K, we define S, a set of triples (ijk):

S = {( ijk) |  kKA( ik)= B( jk)}.

You are to give the value of |S| for specific AB and K.

Input

The input file contains several blocks of data. For each block, the first line contains one integer K, followed by two lines containing strings A and B, respectively. The input file is ended by K=0.

1 ≤ |A|, |B| ≤ 105
1 ≤ K ≤ min{|A|, |B|}
Characters of A and B are all Latin letters.

Output

For each case, output an integer |S|.

Sample Input

2
aababaa
abaabaa
1
xx
xx
0

Sample Output

22
5
题意:给出K和2个串,求这2个串中长度大于等于K的公共字串的个数
思路:很容易看出枚举长度肯定会超时!我想了很久,也没想出来。然后就找题解啊~~结果得用单调栈优化
     先说下大方向,将两个串中间用个没出现过的字符隔开,后面添加一个最小字符。构建后缀数组。
     然后扫描2次,第一扫描算出每个A串后缀与前面的B串后缀的>=k长度的公共字串数
     第二次扫描求出每个B串后缀与前面的A串后缀的>=k长度的公共子串数。
     具体扫描就是,维护一个单调栈(lcp)。比如现在是第一轮扫描。遇到一个新串就得更新之前的lcp。我们首先要清楚
     sa[i]和sa[j]的lcp是夹在其中的这段height[]中的最小值。
     看到这里就相对好想了,我们用一个栈,从栈顶到栈底的lcp依次减小。每扫描一个新的height[],如果栈顶的
     h>=height[i],那就得将其h改为height[i],个数统计起来。直到栈顶的h<height[].然后再将height[i]和个数     (用结构体)入栈。。至于计算>=k的字串数,两个lcp为K的后缀,>=k的字串为K-k+1。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
#define maxn 240080
#define inf 0x3f3f3f3f
#define LL long long int
int str[maxn],vis[maxn];
char s[maxn];
int sa[maxn],t[maxn],t2[maxn],c[maxn];
int height[maxn],Rank[maxn];
int len1,len2;
inline int max(int a,int b)
{
	return a>b?a:b;
}
/*
用SA模板注意在最后添加一个比所有字符都小的字符。
key[n] = 0;
build_sa(key,n+1,m);
getHeight(key,n+1);
显然sa[0] 就是最后那个位置。。。
height[i] 表示 sa[i] 和 sa[i-1] 的最长公共前缀。。
*/

void build_sa(int * s,int n,int m)
{
	int i,*x = t,*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(int k = 1;k <= n;k <<= 1)
	{
		int 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 = 0;i < m;i++)	c[i] += c[i-1];
		for(i = n-1;i >= 0;i--)	sa[--c[x[y[i]]]] = y[i];
		//根据sa和y数组计算新的数y组
		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++)	Rank[sa[i]] = i;
	for(i = 0;i < n;i++)	
	{
		if(k) k--;
		int j = sa[Rank[i]-1];
		while(s[i+k] == s[j+k])		k++;
		height[Rank[i]] = k;
	}
}

struct Item
{
	LL h,num;
	Item(){}
	Item(LL hh,LL nn)
	{
		h = hh;	num = nn;
	}
}S[maxn];
LL query(int n,int a,int k)
{
	LL ans = 0,sum = 0;
	int first = maxn-1,rear = maxn-1;
	for(int i = 1;i < n;i++)
	{
		if(height[i] < k)
		{
			first = rear;
			sum = 0;
			continue;
		}
		LL num = 0;
		while(first < rear && S[first].h >= height[i])
		{
			num += S[first].num;
			sum -= (S[first].h - k + 1)*S[first].num;
			first++;
		}
		S[--first] = Item(height[i],num);
		sum += (height[i] - k + 1)*num;
		if(vis[sa[i-1]] && vis[sa[i-1]]!= a)
		{
			sum += height[i] - k + 1;
			S[first].num++;
		}
		if(vis[sa[i]] == a)
			ans += sum;
		//统计完后就得更新	
	}
	return ans;
}

int main()
{
	//freopen("in.txt","r",stdin);
	int k;
	while(scanf("%d",&k)!=EOF && k)
	{
		scanf("%s",s);
		len1 = strlen(s);
		for(int i = 0;i < len1;i++)
		{
			if(s[i] < 'a')
			str[i] = s[i] - 'A' + 27;
			else str[i] = s[i] - 'a' + 1;
			vis[i] = 1;
		}
		vis[len1] = 0;
		str[len1++] = 54;
		scanf("%s",s);
		len2 = strlen(s);
		for(int i = 0;i < len2;i++)
		{
			if(s[i] < 'a')
			str[len1+i] = s[i] - 'A' + 27;
			else str[len1+i] = s[i] - 'a' + 1;
			vis[len1+i] = 2;
		}
		vis[len1+len2] = 0;
		str[len1+len2] = 0;
		build_sa(str,len1+len2+1,55);
		getHeight(str,len1+len2+1);
		LL ans = 0;
		ans += query(len1+len2+1,1,k);
		ans += query(len1+len2+1,2,k);
		printf("%lld\n",ans);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值