题目链接:http://poj.org/problem?id=3415
后缀数组论文链接:https://wenku.baidu.com/view/5b886b1ea76e58fafab00374.html
解题思路:
求不小于k的最长公共子串的数量。
①
首先连接两个字符串A,B,构造后缀和height数组,我们按照k,将height>=k的分在一起,属于这一堆内的后缀,只有可能和这一堆内的LCP>=k。
②
试想一下暴力的思路,对于块内,枚举内个A后缀,遍历块,求与B后缀的LCP,ans+=LCP-k+1;
这样复杂度是O(N*N)
③
这其中的问题在于没有有效利用LCP(sa[i-1],sa[j]) = min(height[i]~height[j])
④
每一个后缀与之后某一位置的LCP为对应这段区间的内height的最小值,所有我们明显可以用单调队列维护到达当前位置时,之前所有的后缀和当前的(LCP-k+1)之和。(明显个P)
也就是说现在我们可以O(1)统计当前后缀与之前所有前缀的LCP-k+1,但是只能统计当前位置之前的。
于是我们维护两个单调栈,一个用于维护之前所有A后缀与当前位置的(LCP-k+1)和,另一个维护B后缀与当前位置的(LCP-k+1)和
当前是A,就把之前B的和加一下,是B,就把A的和加一下,同时把当前的后缀维护进对应的栈。这样会统计到所有情况:
每个位置保证扫完之前所有的另一个串后缀且保证被之后的所有另一个串后缀扫到。
⑤
然后这个单调队列稍微详细讲一下:
我们每有一个值入栈,会有什么改变?之前所有比当前数大的,全部要改为和当前的数一样大,同时我们的总和也要做出相应的变化,你不可能一个个的修改,所以开两个栈,一个记录当前栈内元素大小,一个记录个数,每次把比当前大的元素踢掉,并记录踢了几个,然后栈顶变成当前元素,对应另一个维护栈内元素的栈也做出相应改变。(现在不会的话自己举个例子试试就会了,真的)
这四个栈应该可以一步到位,怕下标太多搞混淆就先维护A,求一遍B,再维护B,求一边A
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define pb push_back
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f
const int N = 2e5+5;
char s[N];
int wa[N],wb[N],rnk[N],height[N],sa[N];
int sta[N],cnt[N],top;
void buildsa(int n,int m)
{
int i,j,*x = wa,*y = wb,p;
for0(i,0,m) cnt[i] = 0;
for0(i,0,n) cnt[x[i]=s[i]]++;
for0(i,1,m) cnt[i] += cnt[i-1];
for (int i=n-1;i>=0;i--) sa[--cnt[x[i]]] = i;
for (j=1,p=1;p<n;j<<=1,m=p){
for (i=n-j,p=0;i<n;i++) y[p++] = i;
for0(i,0,n) if (sa[i]>=j) y[p++] = sa[i]-j;
for0(i,0,m) cnt[i] = 0;
for0(i,0,n) cnt[x[y[i]]]++;
for0(i,1,m) cnt[i] += cnt[i-1];
for (int i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]] = y[i];
swap(x,y);
x[sa[0]] = 0;
p = 1;
for (i=1;i<n;i++)
x[sa[i]] = (y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j])? p-1:p++;
}
}
void getheight(int n)
{
int k = 0;
for0(i,0,n) rnk[sa[i]] = i;
for0(i,0,n){
if (rnk[i]==0){height[rnk[i]] = k = 0;continue; }
if (k) k--;
int j = sa[rnk[i]-1];
while (i+k<n && j+k<n && s[i+k]==s[j+k]) k++;
height[rnk[i]] = k;
}
}
ll solve(int len,int lenlen,int kk)///height1~heightlenlen
{
ll ans = 0,sum = 0;
for (int i=1;i<=lenlen;i++){///虽然写了好多for,整体复杂度是O(N)
if (height[i]>=kk){
int j;
for (j = i;j+1<=lenlen && height[j+1]>=kk ;j++);//printf("[%d,%d]\n",i,j);
///处理这一段区间的方法数,先处理:B匹配之前的A
top = -1,sum = 0;
for (int k = i;k<=j;k++){
int nowcnt = sa[k-1]>len? 0:1;
while (top>=0 && height[k]<sta[top]) nowcnt+=cnt[top],sum -= (ll)(sta[top]-kk+1)*cnt[top],top--;
if ((top==-1 || height[k]>sta[top]) && nowcnt>0) sta[++top] = height[k],cnt[top] = nowcnt,sum += (ll)(sta[top]-kk+1)*cnt[top];
else if (top>=0 && height[k]==sta[top]) cnt[top] += nowcnt,sum += (ll)(sta[top]-kk+1)*nowcnt;
if (sa[k]>len) ans += sum;//,printf("sum=%d ",sum),debug(+++B find A);
}
///处理:A匹配之前B
top = -1,sum = 0;
for (int k = i;k<=j;k++){
int nowcnt = sa[k-1]<len? 0:1;
while (top>=0 && height[k]<sta[top]) nowcnt+=cnt[top],sum -= (ll)(sta[top]-kk+1)*cnt[top],top--;
if ((top==-1 || height[k]>sta[top]) && nowcnt>0) sta[++top] = height[k],cnt[top] = nowcnt,sum += (ll)(sta[top]-kk+1)*cnt[top];
else if (top>=0 && height[k]==sta[top]) cnt[top] += nowcnt,sum += (ll)(sta[top]-kk+1)*nowcnt;//,printf("sum+=%lld ,sum=%lld\n",(ll)(sta[top]-kk+1)*nowcnt,sum);
if (sa[k]<len) ans += sum;//,printf("sum=%d ",sum),debug(+++A find B);
}
i = j;
}
}
return ans;
}
int main()
{
int k;
while (~scanf("%d",&k),k){
scanf("%s",s);
int len = strlen(s);
s[len] = '#';
scanf("%s",s+len+1);
int lenlen = len + strlen(s+len+1);
buildsa(lenlen+1,300);
getheight(lenlen+1);
//for(int i=1;i<=lenlen;i++) printf("%d, %s height = %d\n",i,s+sa[i],height[i]);
printf("%lld\n",solve(len,lenlen,k));
}
return 0;
}