题目
给你一个串(串长<=1e5),一个k(k>=1)
问串中恰好出现k次的子串有多少个
思路来源
https://www.cnblogs.com/weeping/p/7503972.html
题解
先建后缀数组和high数组,
high[i]代表排名为i和排名为i-1的最长公共前缀
注意到出现k次的子串长度的上界为长度为k-1的区间的最小值
而出现k+1次的子串长度的上界为长度为k的区间的最小值
所以一个长度为k-1的区间对答案的贡献,
就是这段区间把出现k+1次及以上的子串刨去的值
记RMQ(i+1,i+k-1)为这个区间的high的最小值,
那么[i+1,i+k-1]对答案贡献即max(0,RMQ(i+1,i+k-1)-max(RMQ(i,i+k-2),RMQ(i+1,i+k)))
由RMQ(i,i+k-2)=min(high[i],RMQ(i+1,i+k-1)),RMQ(i+1,i+k)=min(high[i+k],RMQ(i+1,i+k-1))知,
后两段区间最小值,二选一,如果是RMQ(i+1,i+k-1)答案显然为0,是另一个才有意义
所以可以化简成max(0,RMQ(i+1,i+k-1)-max(high[i],high[i+k]))
[i+2,i+k]贡献不会与[i+1,i+k-1]贡献算重复
设这两个区间有相同的子串,则交集显然为RMQ(i+1,i+k),
在[i+1,i+k-1]里会被减掉一个不小于RMQ(i+1,i+k)的值,
同理[i+2,i+k]里也会被减掉一个不小于RMQ(i+1,i+k)的值
相同的都被减掉了,所以剩下的相邻的两个区间没有重复
传递性(因为贡献统计的是前缀,离得越远前缀越不同)
知任意两个长为k-1的区间都没有重复
对答案有贡献的区间长度不会小于k-1,
这说明统计贡献的时候只能在长度为k-1的区间里取
统计所有长度为k-1的区间的贡献即可
所有的并是全集,两两的交是空,显然是答案的一个划分
注意特判k==1的情形,
对于i开头的后缀,
有high[i]说明和上一个后缀有公共部分,
有high[i+1]说明下一个后缀和自己有公共部分
该后缀长度把k==2的情形减掉,就是答案
心得
我就是个字符串的辣鸡,什么都不会
以后见到不会的题及时补,别再死抠了
能看懂题解至上,别浪费不必要的时间
有SAM后缀自动机做法,回头再学吧
后续
2020.6.8回来再看这个题,之前自己写的什么垃圾玩意…
①对max的理解
考虑ab,abc,abcd,abcde,abcdef
其中[abc,abcde]出现了3次的前缀为abc,
而[ab,abcde]中出现4次的前缀为ab,[abc,abcdef]中出现4次的前缀为abc
这表明a ab abc都是出现超过3次的串,应该从abc里扣除,
即一个前缀只要在前后被覆盖了一次,它就应该被扣除
②对两个相邻的长度恰好为k的区间无交的理解
如果它们算入了相同的前缀,则这个前缀至少在相邻位置出现了k+1次,与之前被减过矛盾
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N = 1e5+10;
char s[N];
int sa[N], t[N], t2[N], c[N], n;
int Rank[N], high[N];
int dp[N][20];
ll ans;
void build(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, p; k <= n; k<<=1, m=p) {
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 = 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]] = (y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+k] == y[sa[i]+k]) ? p-1 : p++;
}
if(p >= n) break;
}
}
void get_high()
{
int k = 0;
for(int i = 1; i < n; i++) Rank[sa[i]] = i;
for(int i = 0; i < n-1; i++) {
if(k) k--;
int j = sa[Rank[i]-1];
while(s[i+k] == s[j+k]) k++;
high[Rank[i]] = k;
//if(Rank[i]==0)printf("...%d\n",i);
}
}
void ST(int n,int high[])
{
for(int i=1;i<n;++i)dp[i][0]=high[i];
for(int j=1;(1<<j)<n;++j)
{
for(int i=1;i+(1<<j)-1<n;++i)
{
dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
}
int RMQ(int l,int r)
{
int k=log2(r-l+1);
return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
void PR()
{
printf("The rank is:\n");
printf("%d", Rank[0]);
//string p(s);
for(int i = 1; i <= n-1; i++)
{
printf(" %d", Rank[i]);
//cout<<p.substr(sa[i])<<endl;
}
puts("");
}
void init()
{
memset(sa,0,sizeof(sa));
memset(t,0,sizeof(t));
memset(t2,0,sizeof(t2));
memset(c,0,sizeof(c));
memset(Rank,0,sizeof(Rank));
memset(high,0,sizeof(high));
memset(dp,INF,sizeof(dp));
ans=0;
}
int T,k;
int main()
{
scanf("%d",&T);
while(T--)
{
init();
scanf("%d",&k);
scanf("%s", s);
n = strlen(s) + 1;
int maxi = 0;
for(int i = 0; i < n; i++) {
maxi = maxi > s[i] ? maxi : s[i];
}
s[n-1] = 0;
build(maxi+1);
get_high();
//PR()
/*后续操作*/
high[n]=0;
//for(int i=1;i<n;++i)
//printf("%d:%d\n",i,high[i]);
ST(n,high);
for(int i=1;i+k-1<n;++i)
ans+=max(0,(k==1?n-1-sa[i]:RMQ(i+1,i+k-1))-max(high[i],high[i+k]));
printf("%lld\n",ans);
}
return 0;
}