洛谷 P1787 [入门赛 #22] 非众数 Hard Version题解

P1787 [入门赛 #22] 非众数 Hard Version题解

此蒟蒻的第一篇题解,有问题请指出,不喜轻喷!

声明:代码及思路的提供来自于@pjh0625

1. 读题

题目要求计算给定字符串中非众数子串的数量
非众数子串的定义是:子串中出现次数最多的字符的频率不超过子串长度的一半。
非众数串的定义是:一个字符串 s 中,没有任何字符的出现次数超过字符串长度的一半。

2. 解题思路

直接暴力解法(遍历所有子串并判断)的时间复杂度为o( n 2 n^2 n2) ,在 n ≤ 1 0 5 n≤10^5 n105 的数据范围内会超时。因此,我们需要一种更高效的算法。
树状数组(Binary Indexed Tree, BIT)

  1. 树状数组的作用:
    • 树状数组可以高效地维护和查询前缀和,时间复杂度为 O ( l o g n ) O(logn) O(logn)
    • 通过树状数组,我们可以快速统计子串中某个字符的频率。
  2. 离散化处理:
    • 为了处理负数索引问题,我们将每个字符的频率和位置信息离散化为正整数。
    • 具体来说,对于每个字符 c我们使用 2 * sum - j + n作为树状数组的索引,其中 sum 是当前字符的频率,j 是当前索引。
  3. 动态计算众数子串:
    • 对于每个字符(az),遍历字符串中的每个位置。
    • 使用树状数组统计以该字符为“众数”的子串数量。
    • 最终,通过总子串数量减去众数子串数量,得到非众数子串的数量。

3.代码逻辑:

  1. 初始化
    • 输入字符串并计算其长度。
    • 初始化树状数组 t 和众数子串数量cnt
  2. 遍历每个字符(az):
    • 对于每个字符c,初始化树状数组并计算其频率。
    • 遍历字符串中的每个位置 j,动态更新树状数组。
  3. 动态更新树状数组:
    • 如果当前字符是目标字符,频率 sum 加1。
    • 使用树状数组查询当前子串的贡献,并更新众数子串数量 cnt
    • 更新树状数组,将当前频率和位置信息离散化后存入树状数组。
  4. 计算非众数子串数量:
    • 总子串数量为 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)
    • 非众数子串数量 = 总子串数量 - 众数子串数量。

5.代码实现

  1. 头文件和变量定义
#include<bits/stdc++.h>//万能头文件
using namespace std;
const int maxn = 3e5 + 5; // 定义最大字符串长度
typedef long long ll;//重定义,将 long long 定义为 ll(懒得写)
int n, t[maxn]; // n 为字符串长度,t 为树状数组
ll cnt; // 用于记录众数子串的数量
char s[maxn]; // 存储输入的字符串
  1. 自定义函数函数
inline int lowbit(int x) { return x & -x; }
// 更新树状数组
inline void gx(int x) {
	for (; x<=n*3;x+=lowbit(x)) t[x]++;
}
// 查询树状数组的前缀和
inline int cx(int x,int res=0) {
	for (; x;x-=lowbit(x))res+=t[x];
	return res;
}
  1. 输入字符串及获取长度(已进入主函数)
  scanf("%s", s + 1);// 输入字符串
  n=strlen(s + 1); // 获取字符串长度

cincout的同学加上这两句:

//关闭同步不流,为cin和cout加速
ios_base::sync_with_stdio(false);
cin.tie(nullptr)
  1. 遍历每个字符(a-z)
for (int i = 0; i < 26; i++) {
		memset(t, 0, sizeof t); // 初始化树状数组
		int sum = 0; // 当前字符的频率
		// 遍历字符串中的每个位置
		for (int j = 0; j <= n; j++) {
			if (s[j] == i + 'a') sum++; // 如果当前字符是目标字符,频率加一
			
			// 计算当前子串的贡献
			// 2*sum - j + n 是离散化后的值,用于避免负数
			cnt += cx(2 * sum - j + n);
			
			// 更新树状数组
			gx(2 * sum - j + n + 1);
		}
	}

5.输出及结束

	// 总子串数量 - 众数子串数量 = 非众数子串数量
	printf("%lld", 1ll * (n + 1) * n / 2 - cnt);
	return 0;//养成好习惯

以下是完整的代码,不会的同学可以参考仅供参考
爱惜账号,从我做起

#include<bits/stdc++.h>//万能头文件
using namespace std;
const int maxn = 3e5 + 5; // 定义最大字符串长度
typedef long long ll;//重定义,将 long long 定义为 ll(懒得写)
int n, t[maxn]; // n 为字符串长度,t 为树状数组
ll cnt; // 用于记录众数子串的数量
char s[maxn]; // 存储输入的字符串
// 计算树状数组的 lowbit
inline int lowbit(int x) { return x & -x; }
// 更新树状数组
inline void gx(int x) {
	for (; x<=n*3;x+=lowbit(x)) t[x]++;
}
// 查询树状数组的前缀和
inline int cx(int x,int res=0) {
	for (; x;x-=lowbit(x))res+=t[x];
	return res;
}
int main() {
	scanf("%s", s + 1);// 输入字符串
	n=strlen(s + 1); // 获取字符串长度
	// 遍历每个字符(a-z)
	for (int i = 0; i < 26; i++) {
		memset(t, 0, sizeof t); // 初始化树状数组
		int sum = 0; // 当前字符的频率
		// 遍历字符串中的每个位置
		for (int j = 0; j <= n; j++) {
			if (s[j] == i + 'a') sum++; // 如果当前字符是目标字符,频率加一
			// 计算当前子串的贡献
			// 2*sum - j + n 是离散化后的值,用于避免负数
			cnt += cx(2 * sum - j + n);
			
			// 更新树状数组
			gx(2 * sum - j + n + 1);
		}
	}
	// 总子串数量 - 众数子串数量 = 非众数子串数量
	printf("%lld", 1ll * (n + 1) * n / 2 - cnt);
	return 0;//养成好习惯
}

通过记录
看在


的份上,点个再走吧!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

—海燕—

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值