有一个明显的性质:如果子串(i,j)包含了至少kkk个不同的字符,那么子串(i,k),(j<k<length)也包含了至少k个不同字符。
因此对于每一个左边界,只要找到最小的满足条件的右边界,就能在O(1)时间内统计完所有以这个左边界开始的符合条件的子串。
寻找这个右边界,是经典的追赶法(尺取法,双指针法)问题。维护两个指针(数组下标),轮流更新左右边界,同时累加答案即可。复杂度 O(length(S))
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[1000100];
int vis[1000];
int main()
{
int T,n,k;
while(~scanf("%d",&T))
{
while(T--)
{
scanf("%s",s);
scanf("%d",&k);
long long num=0;
int len=strlen(s);
int biao=0,j=0;
memset(vis,0,sizeof(vis));
for(int i=0; i<len; i++)
{
while(1)
{
if(i+j>=len)
break;
if(biao>=k) ///存在一开始就满足情况的
break;
if(!vis[s[i+j]]) ///不存在就标记
{
vis[s[i+j]]++;
biao++;
}
else
vis[s[i+j]]++;
if(biao>=k)
break;
j++;
}
if(i+j>=len)break;
vis[s[i]]--;
if(biao>=k)
num=num+len-i-j;
if(vis[s[i]]==0)
biao--; ///说明左指针指向的数是独一无二的
else
j--; /// 说明左指针指向的数中间会存在,让右指针位置不变
//num=num+len-i-j;
//j--;
}
printf("%lld\n",num);
}
}
return 0;
}
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
char c[1000010];
int a[50],f[1000010];
int main()
{
int T,n,k,i,l,p;
scanf("%d",&T);
while(T--)
{
memset(f,0,sizeof(f));
memset(a,0,sizeof(a));
scanf("%s",c+1);
scanf("%d",&k);
n=strlen(c+1);
p=0;l=1;
for(i=1;i<=n;i++)
{
if(a[c[i]-'a']==0)p++;
a[c[i]-'a']++;
while(p>=k)
{
a[c[l]-'a']--;
if(a[c[l]-'a']==0)p--;
f[l]=i;
l++;
}
}
long long ans=0;
for(i=1;i<=n;i++)
if(f[i])ans+=n-f[i]+1;
printf("%I64d\n",ans);
}
return 0;
}