洛谷 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 2le nle 10^5 2n105,字符集为 [ a , z ] ∪ [ A , Z ] [a,z]∪[A,Z] [a,z][A,Z]

题解

  • 会发现一个区间如果满足条件,那么每种字符前缀和在 r r r位置和 l − 1 l-1 l1位置之差都相等,
  • 换种角度考虑, r r r位置和 l − 1 l-1 l1位置下,每两种字符前缀和之差都相等,
  • 所以可以把前缀和每个位置的各个字符和字符 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;
}
原文地址:https://www.cnblogs.com/LZA119/p/14279512.html