洛谷 P6273 [eJOI2017]魔法(前缀和+排序)
洛谷 P6273 [eJOI2017]魔法
题目
- 给定一个长度为 n n n的字符串 S S S。设 S S S的不同的字符数为 k k k。
- 定义字符串的子串为该字符串某一连续段。
- 而有魔法的子串被定义为 S S S的某一非空子串,满足该子串中不同的字符数为 k k k,且每个字符的出现的次数都相同。
- 你需要求出给定字符串 S S S的不同的有魔法的子串的个数。
- 若两个子串的左右端点不同,则这两个子串不同。
- 对于所有数据,保证 2 ≤ n ≤ 1 0 5 2\le n\le 10^5 2≤n≤105,字符集为 [ a , z ] ∪ [ A , Z ] [a,z]∪[A,Z] [a,z]∪[A,Z]。
题解
- 会发现一个区间如果满足条件,那么每种字符前缀和在 r r r位置和 l − 1 l-1 l−1位置之差都相等,
- 换种角度考虑, r r r位置和 l − 1 l-1 l−1位置下,每两种字符前缀和之差都相等,
- 所以可以把前缀和每个位置的各个字符和字符 a a a的值之差列出来,相同的两个位置代表的区间即符合条件,直接排序即可。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100010
char s[N];
int f[55], p[55], r = 0;
struct node {
int p[55];
}a[N];
int cmp(node x, node y) {
for(int i = 0; i < r; i++) {
if(x.p[i] < y.p[i]) return 1;
if(x.p[i] > y.p[i]) return 0;
}
return 0;
}
int main() {
int n, i, j;
scanf("%d", &n);
scanf("%s", s + 1);
for(i = 1; i <= n; i++) {
if(s[i] <= 'Z') p[s[i] - 'A'] = 1;
else p[s[i] - 'a' + 26] = 1;
}
for(i = 0; i < 52; i++) {
if(p[i]) p[i] = r, r++;
}
for(i = 1; i <= n; i++) {
if(s[i] <= 'Z') f[p[s[i] - 'A']]++;
else f[p[s[i] - 'a' + 26]]++;
for(j = 0; j < r; j++) a[i].p[j] = f[j] - f[0];
}
sort(a, a + n + 1, cmp);
long long ans = 0, t = 0;
for(i = 0; i <= n; i++) {
if(i == 0 || cmp(a[i - 1], a[i]) == 1) {
t = 1;
}
else {
ans += t;
t++;
}
}
printf("%lld", ans % 1000000007);
return 0;
}
哈哈哈哈哈哈哈哈哈哈