poj3415 Common Substrings

Description

A substring of a string T is defined as:
T ( i , k ) = T i T i + 1 . . . T i + k − 1 , 1 ≤ i ≤ i + k − 1 ≤ ∣ T ∣ . T( i, k)= T_iT_{i +1}... T_{i+k -1}, 1≤ i≤ i+k-1≤| T|. T(i,k)=TiTi+1...Ti+k1,1ii+k1T.

Given two strings A, B and one integer K, we define S, a set of triples (i, j, k):
S = {( i, j, k) | k≥ K, A( i, k)= B( j, k)}.

You are to give the value of |S| for specific A, B 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

Source
POJ Monthly–2007.10.06, wintokk

sol:
题目是让我们求A的后缀和B的后缀的所有lcp中lcp>=K的lcp-k的和。
用后缀数组来做
考虑维护一个并查集,并起来的点是一系列相邻的点,并查集中的元素是这个集合中最靠左的点,最左边点的hei,集合中A的后缀的数量numa,B的后缀的数量numb,将hei从大到小来处理,对每次处理的hei,将这个hei指向的两个相邻的点对应的并查集计算贡献,并且合并。
因为hei从大到小考虑,所以一个并查集中的hei一定是大于当前枚举的hei的,当前的hei可以控制左右两个集合的hei。
类似分治的计算贡献
但本质上是在后缀树上做dp

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int N=210000;

int T,n,m;
int sa[N],rk[N],w[N],x[N],hei[N];
char s[N];
int sr[N];

inline void build_sa()
{
	memset(x,0,sizeof(x));
	memset(w,0,sizeof(w));
	memset(sa,0,sizeof(sa));
	memset(rk,0,sizeof(rk));
	memset(hei,0,sizeof(hei));
	int m=4200;
	for(int i=1;i<=n;++i) ++w[x[i]=sr[i]];
	for(int i=1;i<=m;++i) w[i]+=w[i-1];
	for(int i=n;i>=1;--i) sa[w[x[i]]--]=i;
	for(int j=1;j<=n;j<<=1)
	{
		int cnt=0;
		for(int i=n-j+1;i<=n;++i) rk[++cnt]=i;
		for(int i=1;i<=n;++i)
		if(sa[i]-j>0) rk[++cnt]=sa[i]-j;
		for(int i=1;i<=m;++i) w[i]=0;
		for(int i=1;i<=n;++i) ++w[x[i]];
		for(int i=1;i<=m;++i) w[i]+=w[i-1];
		for(int i=n;i>=1;--i) sa[w[x[rk[i]]]--]=rk[i];
		m=0;
		for(int i=1;i<=n;++i)
		{
			int u=sa[i],v=sa[i-1];
			if(x[u]!=x[v]||x[u+j]!=x[v+j]) ++m;
			rk[u]=m;
		}
		if(m==n) break;
		for(int i=1;i<=n;++i) x[i]=rk[i];
	}
	int j=0;
	for(int i=1;i<=n;++i)
	{
		j=max(j-1,0);
		int u=sa[rk[i]-1];
		while(sr[u+j]==sr[i+j]) ++j;
		hei[rk[i]]=j;
	}
}
int belong[N];
struct cc
{
	int hei,pos;
	cc(){}
	cc(int x,int y){hei=x;pos=y;}
	friend bool operator <(const cc &a,const cc &b)
	{
		return a.hei<b.hei;
	}
};
priority_queue<cc> q;
int fa[N];
int numa[N],numb[N];
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unit(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy)
	{
		fa[fy]=fx;
		numa[fx]+=numa[fy];
		numb[fx]+=numb[fy];
	}
}

inline void solve()
{
	n=0;
	int cnt=200;
	memset(numa,0,sizeof(numa));
	memset(numb,0,sizeof(numb));
	memset(belong,0,sizeof(belong));
	for(int i=1;i<=2;++i)
	{
		cin.getline(s+1,N,'\n');
		int t=strlen(s+1);
		for(int j=1;j<=t;++j)
		{
			sr[++n]=s[j];
			belong[n]=i;
		}
		sr[++n]=++cnt;
		belong[n]=i;
	}
	build_sa();
	for(int i=1;i<=n;++i)
	{
		fa[i]=i;
		if(sr[sa[i]]>=200) continue;
		numa[i]=belong[sa[i]]==1;
		numb[i]=belong[sa[i]]==2;
	}
	
	
	while(q.size()) q.pop();
	for(int i=1;i<=n;++i)
		q.push((cc){hei[i],i});
	ll ans=0;
	while(q.size()>1)
	{
		cc a,b;
		a=q.top();
		q.pop();
		int x=find(a.pos);
		if(hei[x]<m) break;
		if(x==1) continue;
		int y=find(x-1);
		ans+=(ll)numa[y]*numb[x]*(hei[x]-m+1);
		ans+=(ll)numb[y]*numa[x]*(hei[x]-m+1);
		unit(y,x);
	}
	cout<<ans<<endl;
}

int main()
{
	while(1)
	{
		cin.getline(s+1,300,'\n');
		n=strlen(s+1);
		m=0;
		for(int i=1;i<=n;++i) m=m*10+s[i]-'0';
		if(!m) break;
		solve();
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值