传送门:HDU6194
题意:问给定字符串中有多少种出现k次的子串。
思路:首先想到后缀数组经典问题,求出现不少于k次的子串的最大长度,类似的这题肯定就是在height数组上搞事情啦。
将height数组每相邻k - 1个一组,这k - 1个height[i]中最小的那个设为tmp,就表示这排名相邻的k个后缀的最长公共前缀(设为s)长度为tmp,可以得出s出现了至少k次,那么长度为tmp - 1,tmp - 2 ...1的s的前缀必定也出现了至少K次,要确定这些串是否严格出现了k次,还要判断这相邻k - 1个height的前一个height值和后一个height值,分别设为height[i] 和 height[j],则长度为 1 -- max(height[i], height[j]) 的s的前缀出现了至少k + 1次,因此要去掉这部分。
对于每相邻k - 1个一组的height都求一下对答案的贡献就好了。
至于求相邻k - 1个height的最小值, 那就各显神通了,什么ST表,线段树都可以啦(刚刚想起来因为是连续求最小值,单调队列(滑动窗口)好像也可以)。
以上办法只能解决k >= 2的问题,k == 1的时候是无法解决的(可以自己想想height数组的定义),比赛的时候蒟弱就是卡死在这了。。两个小时愣是没想出来k == 1该怎么解。。
赛后突然灵光一现,我们只要求出所有不同子串的个数,然后减去那些出现大于等于两次不就行了么。。前几天还刚做过后缀数组求不同子串个数(传送门:点击打开链接)。。也是后缀经典用法,不多说了,问题是怎么减去出现大于等于两次的串的个数,height数组啊!height数组啊!height数组啊!(注意不要减重了)
代码:
#include<bits/stdc++.h>
#define ll long long
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
#define inf 0x3f3f3f3f
using namespace std;
const int MAXN = 100010;
int t1[MAXN], t2[MAXN], c[MAXN];
int ra[MAXN], height[MAXN];
int sa[MAXN];
char str[MAXN];
int n;
bool cmp(int *r, int a, int b, int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(char str[], int sa[], int ra[], int height[], int n, int m)
{
n++;
int i, j, p, *x = t1, *y = t2;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[i]=str[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(j = 1; j <= n; j<<=1)
{
p = 0;
for(i = n-j; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;
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]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;
if(p >= n) break;
m = p;
}
int k = 0;
n--;
for(i = 0; i <= n; i++) ra[sa[i]] = i;
for(i = 0; i < n; i++)
{
if(k) k--;
j = sa[ra[i]-1];
while(str[i+k]==str[j+k]) k++;
height[ra[i]] = k;
}
}
int num[MAXN << 2];
void build(int l, int r, int rt)
{
if(l == r)
{
num[rt] = height[l];
return ;
}
int mid = (l + r) >> 1;
build(lson);
build(rson);
num[rt] = min(num[rt << 1], num[rt << 1 | 1]);
}
int query(int L, int R, int l, int r, int rt)
{
if(L <= l && r <= R)
{
return num[rt];
}
int mid = (l + r) >> 1, ans = inf;
if(L <= mid)
ans = min(ans, query(L, R, lson));
if(R > mid)
ans = min(ans, query(L, R, rson));
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,k;
int T;
cin>>T;
while(T--)
{
scanf("%d",&k);
scanf(" %s", str);
n=strlen(str);
da(str, sa, ra, height, n, 128);
build(1, n, 1);
int last = 2;
ll ans = 0;
if(k == 1)
{
//ans = 1ll * (n + 1) * n / 2;
for(int i = 1; i <= n; i++)
{
ans += n - sa[i] - height[i];
if(height[i] > height[i - 1])
ans -= (height[i] - height[i - 1]);
}
cout << ans << endl;
continue;
}
for(int i = 2; i <= n; i++)
{
if(i - k + 2 < 2) continue;
int tmp = query(i - k + 2, i, 1, n, 1), tmp2;
if(i - k + 1 < 2) tmp2 = 0;
else tmp2 = height[i - k + 1];
if(i + 1 <= n)
tmp2 = max(tmp2, height[i + 1]);
if(tmp > tmp2)
ans += tmp - tmp2;
}
printf("%lld\n",ans);
}
return 0;
}