写在前面
题目来源:AcWing 寒假每日一题2023活动
链接:https://www.acwing.com/problem/content/description/4264/
题目
Farmer John 最近购入了 N 头新的奶牛,每头奶牛的品种是更赛牛(Guernsey)或荷斯坦牛(Holstein)之一。
奶牛目前排成一排,Farmer John 想要为每个连续不少于三头奶牛的序列拍摄一张照片。
然而,他不想拍摄这样的照片,其中只有一头牛的品种是更赛牛,或者只有一头牛的品种是荷斯坦牛——他认为这头奇特的牛会感到孤立和不自然。
在为每个连续不少于三头奶牛的序列拍摄了一张照片后,他把所有「孤独的」照片,即其中只有一头更赛牛或荷斯坦奶牛的照片,都扔掉了。
给定奶牛的排列方式,请帮助 Farmer John 求出他会扔掉多少张孤独的照片。
如果两张照片以不同位置的奶牛开始或结束,则认为它们是不同的。
输入格式
输入的第一行包含 N。
输入的第二行包含一个长为 N 的字符串。如果队伍中的第 i 头奶牛是更赛牛,则字符串的第 i 个字符为 G。否则,第 i 头奶牛是荷斯坦牛,该字符为 H。
输出格式
输出 Farmer John 会扔掉的孤独的照片数量。
数据范围
3≤N≤5×105
输入样例:
5
GHGHG
输出样例:
3
样例解释
这个例子中的每一个长为 3 的子串均恰好包含一头更赛牛或荷斯坦牛——所以这些子串表示孤独的照片,并会被 Farmer John 扔掉。
所有更长的子串(GHGH、HGHG 和 GHGHG)都可以被接受。
我的超时代码 过了11/12的数据
/**
* 2023年1月12日15:04:41——2023年1月12日16:08:20
二分类,那用0和1分别表示G 和 H,用前缀和的思想做!
* 灵光乍现
* 时间复杂度为:O(N*N) 10次方量级,超时啦,但是这是目前想到最优的了
* 结果:通过11/12的数据 哈哈 还行
*
* 再思考一下怎么优化!
* 2023年1月12日16:08:27——
*/
#include <iostream>
using namespace std;
const int N = 5 * 1e5 + 10;
char c;
int a[N], s[N]; // a是0 1数组,s是前缀和数组下标从1开始,为了省去特判
int main()
{
int n;
scanf("%d", &n);
scanf("%c", &c); // 吃缓冲区的回车
for (int i = 0; i < n; i ++ )
{
scanf("%c", &c);
if (c == 'H') a[i + 1] = 1;
else a[i + 1] = 0;
}
// 计算数组a的前缀和,O(N)
for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i];
// 测试开始
// for (int i = 1; i <= n; i ++ ) cout << a[i] << " ";
// cout << endl;
// for (int i = 1; i <= n; i ++ ) cout << s[i] << " ";
// 测试结束
long long cnt = 0;
// 遍历长度为3,4,5,,,n
for (int len = 3; len <= n; len ++ )
{
for (int i = 1; i + len - 1 <= n; i ++ )
{
int sum_sequence = s[i + len - 1] - s[i - 1]; // 从i这个起点开始的长度为len的序列和为sum_sequence
int cnt_H = sum_sequence, cnt_G = len - cnt_H; // H的个数就是1的个数,G的个数是剩余的
if (cnt_H == 1 || cnt_G == 1) cnt ++ ;
}
}
printf("%lld\n", cnt);
return 0;
}
听完Y总讲解之后
思路
总的思路:枚举只包含一个孤独字母的连续序列,就能不重不漏。
枚举:
情况1——第0头牛当孤独牛,有几张孤独照片
情况2——第1头…
情况3——第2头…
情况4——第3头…
…
求和上述情况。
注:图片来自AcWing
注:L和R必须是连续出现的H 的个数,碰到H就要停止,因为不连续就会引入新的G,导致G的个数大于1个啦,G就不孤独了,G就有小伙伴G了
一开始理解错了,噌噌噌写了一堆,然后Wrong Answer哈哈。
看了一下题解,知道错在哪了(/捂脸)
l数组:表示当前位置左边有连续的几个异类牛
r:右边,同上
代码2
自己理解完的AC代码,缺点是找左右的异类牛数目需要循环,太慢,但是第12个50000的数据倒是过了
#include <iostream>
using namespace std;
const int N = 5*1e6 + 10;
char c[N];
int main()
{
int n;
scanf("%d", &n);
scanf("%c", &c); // 吃缓冲区的回车
for (int i = 0; i < n; i ++ ) scanf("%c", &c[i]);
long long int ans = 0;
for (int i = 0; i < n; i ++ )
{
// 往左走,有几个连续的 不同种类的牛牛
int cnt_left = 0;
for (int l = i - 1; l >= 0; l -- )
{
if (c[l] == c[i]) break; // 碰到同类牛牛,跳出循环,不连续了
else cnt_left ++ ; // 不同类牛牛,加上
}
// 往右走,有几个连续的 不同种类的牛牛
int cnt_right = 0;
for (int r = i + 1; r < n; r ++ )
{
if (c[r] == c[i]) break; // 碰到同类牛牛,跳出循环,不连续了
else cnt_right ++ ; // 不同类牛牛,加上
}
// 统计个数
ans = ans + max(cnt_right-1, 0) + max(cnt_left-1, 0) + ((long long)cnt_right * (long long)cnt_left);
// cout << i << " 这是一个循环 " << ans << endl;
}
printf("%lld\n", ans);
return 0;
}
代码 3 Y总的思路
用y总思路的L[ ]数组和R[ ]数组
L数组是什么?L[i] :第i头牛左侧的连续异类牛个数
R数组同理,第i头牛右侧的连续异类牛个数。
得到L R数组即可,分别需要一层循环。