题意:
求两个字符串中长度不小于k的公共子串个数。
做法:
即求sigma(两个字符串的后缀的lcp-k+1|lcp>=k)。
首先用一个没有出现过的字符拼接两个串。跑出sa和height.
两个后缀的lcp为在它们排名之间的height的最小值。
所以我们可以对于某一个height,求出它作为最小值的一个区间l[i],r[i],然后最后对于所有>=k的height统计它的贡献。
那么就可以用单调栈搞搞了。
统计答案的时候,由于只有在不同的两个串里的后缀才可以算进答案,所以我们还需预处理出pre[i]。
我们对于在第二个串里的起点都打标记1,pre[i]表示前缀和。
这样假如对于一个height[i]>=k的位子i,对答案的贡献就应当是:
(sum1(l[i],i−1)×sum0(i,r[i])+sum0(l[i],i−1)×sum1(i,r[i]))×(height[i]−k+1)
sum1(x,y)表示[x,y]中标记1的个数,sum0同理。
当然由于height[i]表示的是排名i和排名i-1的lcp,相当于像一个插空一样的存在,所以某些细节可能需要注意,诸如循环从1还是2开始,某些地方i要-1,某些地方不用-1……等等。
代码:
/*************************************************************
Problem: poj 3415 Common Substrings
User: bestFy
Language: C++
Result: Accepted
Time: 797MS
Memory: 7608K
Submit_Time: 2018-01-18 17:08:19
*************************************************************/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cctype>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 200010;
int n, m, k, len1, all, top;
char s[N], s1[N];
int rk[N], tp[N], h[N], sa[N], tong[N], st[N], l[N], r[N], pre[N];
inline void ssort() {
for(int i = 0; i <= all; i ++) tong[i] = 0;
for(int i = 1; i <= n; i ++) tong[rk[tp[i]]] ++;
for(int i = 1; i <= all; i ++) tong[i] += tong[i-1];
for(int i = n; i >= 1; i --) sa[tong[rk[tp[i]]] --] = tp[i];
}
inline void get_sa() {
for(int i = 1; i <= n; i ++) rk[i] = s[i], tp[i] = i;
all = 127; ssort(); int w = 1; all = 1;
while(all < n) {
int t = 0;
for(int i = n-w+1; i <= n; i ++) tp[++ t] = i;
for(int i = 1; i <= n; i ++) if(sa[i]>w) tp[++ t] = sa[i]-w;
ssort(); for(int i = 1; i <= n; i ++) tp[i] = rk[i];
rk[sa[1]] = all = 1;
for(int i = 2; i <= n; i ++)
rk[sa[i]] = (tp[sa[i]] == tp[sa[i-1]] && tp[sa[i]+w] == tp[sa[i-1]+w])?all:++ all;
w <<= 1;
} int k = 0;
for(int i = 1; i <= n; i ++) {
if(k) k --; int j = sa[rk[i]-1];
for(; i+k<=n && j+k<=n && s[i+k] == s[j+k]; k ++);
h[rk[i]] = k;
}
}
int main() {
while(~scanf("%d", &k) && k) {
scanf("%s%s", s+1, s1+1);
len1 = n = strlen(s+1); s[++ n] = 'z'+1;
m = strlen(s1+1);
for(int i = 1; i <= m; i ++) s[++ n] = s1[i];
get_sa();
for(int i = 1; i <= n; i ++) {
pre[i] = pre[i-1];
if(sa[i] <= len1) pre[i] ++;
}
st[top = 0] = 1;
for(int i = 2; i <= n; i ++) {
while(top && h[st[top]] >= h[i]) top --;
l[i] = st[top]; st[++ top] = i;
}
st[top = 0] = n+1;
for(int i = n; i >= 2; i --) {
while(top && h[st[top]] > h[i]) top --;
r[i] = st[top]-1; st[++ top] = i;
}
ll ans = 0;
for(int i = 2; i <= n; i ++) if(h[i] >= k) {
int lft = i-1-l[i]+1, rgt = r[i]-i+1, val = h[i]-k+1;
ans += (ll)(pre[i-1]-pre[l[i]-1])*(rgt - (pre[r[i]]-pre[i-1]))*val;
ans += (ll)(lft - (pre[i-1]-pre[l[i]-1]))*(pre[r[i]]-pre[i-1])*val;
}
printf("%lld\n", ans);
}
return 0;
}