题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤105。
每组数据输入n(1≤n≤105)和长为n字符串s,只包含字符0
到9
。
输出s有多少个连续子串t,满足t中每个字符的出现次数,都不超过t中的字符种类数。
输入样例
7
1
7
2
77
4
1010
5
01100
6
399996
5
23456
18
789987887987998798
输出样例
1
2
10
12
10
15
106
算法
枚举
感觉这道题的做法还挺暴力的,就在超时的边缘,但提交上去跑得还挺快。注意到s串中只有0
-9
这10个数字,因此一个子串中最多就是10种数,每种数出现10次的话长度最大就是100。
因此,先预处理出每个数字在s串中出现次数的前缀和presum[digit]。对于长度为1的所有子串,肯定都满足要求,枚举长度在[2,100]的所有子串,对于任意一个子串[l,r],遍历[0,9]中的所有整数,计算每个数字的频数,看是不是最大频数不超过数字种数即可。
复杂度分析
时间复杂度
预处理前缀和数组的时间复杂度为O(nΣ),其中Σ表示字符集大小,本题中为10。后续枚举子串长度的时间复杂度为min(n,100),滑动窗口的时间复杂度为O(n),枚举数字的时间复杂度为O(Σ),因此算法整体的时间复杂度为O(min(n,100)nΣ)。
空间复杂度
除去输入的字符串s,空间瓶颈在于前缀和数组presum。每个数字都有一个O(n)级别的前缀和数组,因此额外空间复杂度为O(nΣ).
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int t, n, presum[10][N];
char s[N];
void solve() {
for(int digit = 0; digit <= 9; digit++) {
for(int i = 1; i <= n; i++) {
int d = s[i] - '0';
presum[digit][i] = presum[digit][i - 1] + (d == digit? 1: 0);
}
}
int ans = n;
for(int len = 2; len <= min(n, 100); len++) {
for(int r = len; r <= n; r++) {
int l = r - len + 1, cnt = 0, mx = 0;
for(int digit = 0; digit <= 9; digit++) {
int t = presum[digit][r] - presum[digit][l - 1];
if(t > 0) {
mx = mx < t? t: mx;
cnt++;
}
}
if(mx <= cnt) ans++;
}
}
printf("%d\n", ans);
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
scanf("%s", s + 1);
solve();
}
return 0;
}