String
Accepts: 84
Submissions: 373
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/65536 K (Java/Others)
问题描述
有一个 10≤长度≤1,000,000 的字符串,仅由小写字母构成。求有多少个子串,包含有至少k(1≤k≤26)个不同的字母?
输入描述
输入包含多组数据. 第一行有一个整数T(1≤T≤10), 表示测试数据的组数. 对于每组数据: 第一行输入字符串S。 第二行输入一个整数k。
输出描述
对于每组数据,输出符合要求的子串的个数。
输入样例
2 abcabcabca 4 abcabcabcabc 3
输出样例
0 55
String
有一个明显的性质:如果子串(i,j)包含了至少k个不同的字符,那么子串(i,k),(j<k<length)也包含了至少k个不同字符。
因此对于每一个左边界,只要找到最小的满足条件的右边界,就能在O(1)时间内统计完所有以这个左边界开始的符合条件的子串。
寻找这个右边界,是经典的追赶法(尺取法,双指针法)问题。维护两个指针(数组下标),轮流更新左右边界,同时累加答案即可。复杂度 O(length(S))。
AC代码:
#include<iostream> #include<functional> #include<algorithm> #include<cstring> #include<string> #include<vector> #include<cstdio> #include<queue> #include<cmath> #include<map> #include<set> using namespace std; #define CRL(a) memset(a,0,sizeof(a)) #define QWQ ios::sync_with_stdio(0) #define inf 0x3f3f3f3f typedef unsigned long long LL; typedef long long ll; const int T = 1000000+50; const int mod = 1000000007; const int mo = 772002+233; char s[T]; int vis[T]; int main() { #ifdef zsc freopen("input.txt","r",stdin); #endif int n,m,i,j,k; ~scanf("%d",&n); while(n--) { memset(vis,0,sizeof(vis)); scanf("%s",s+1); scanf("%d",&m); int cnt = 0,cur=0;//当前出现的字母次数,当前的头指针 ll ans = 0;//答案数 for(i=1;s[i];++i){ int t = s[i]-'a'; if(!vis[t]++)cnt++;//字符第一次出现 while(cnt>=m&&cur<i)//当出现的次数符合题意 if(cur++,!(--vis[s[cur]-'a']))cnt--;//不断收窄区间,直到不符合条件 ans += cur;//因为是从【1,len(s)】的范围,所以符合的区间其实就是cur的数吧 } printf("%lld\n",ans); } return 0; }