哈喽,大家好啊,可爱的宝子们,在讲解正文之前我们不妨来娱乐一下,不知道你们有没有看过最近非常火的一部动漫——《斩神》
今天这部分文章的开头就给大家介绍一下《斩神》中人气非常高的一位神明,她就是古希腊五位创世神之一,又被称为众神之母的黑夜女神——倪克斯.
哈哈,就让我们欣赏一下这位美丽的女神吧!
1.
2.
进入正题!
问题描述:
王母娘娘举办蟠桃会,却未邀请孙悟空。这使得孙悟空怒不可遏他当即决定大闹蟠桃园,要将蟠桃据为己有。
在蟠桃园中,有 N 条小路,编号从1到 N。这些小路构成了一个环形网络,具体来说,小路之与小路i+1(i<n)相互连接小路 N 与小路 1相互连接。每条小路都可能有天兵把守。
为了加快采摘蟠桃的速度,孙悟空施展法术,变出了N 个分身,编号从1到 N。每个分身起初皆立于其对应编号的小路上,且所采摘的蟠桃数量均为 0。每当孙悟空的本体挥动一次金箍棒,所有的分身就都会向右移动到下一条小路,并出现以下情况之一。
如果一个分身走入有天兵把守的小路,它采摘的蟠桃数量将变为 0。如果一个分身走入没有天兵把守的小路,它采摘的的蟠桃数量将增加 1。
孙悟空的本体能够任意次数地挥动金箍棒(包括0次)
请问,在多次(包括0次)挥动金棒后,所有分身所采摘的蟠桃数量之和的最大可能值是多少?
输入格式:
输入包括两行。
第一行输入一个整数 N(2 ≤ N ≤ 105),表示道路的数量,
第二行输入一个仅包含 工、Q,且长度为 N 的字符串 S,其中若S;为 工 表示道路i有天兵把守,为 Q 表示道路讠没有天兵把守(数据保证至少有1条道路有天兵把守)。
输出格式:
输出一个整数,代表所有分身采摘蟠桃数量之和的最大可能值。
样例输入:
3
L Q L
样例输出:
1
解题思路:
为了更好地理解并解决这个问题,我们可以通过一些巧妙的定义和方法来简化问题的求解过程。首先,我们不需要单独记录每个分身在每个时刻的采摘数量,而是引入一个新的变量c[i],表示当前位于小路i的分身的采摘数量。
定义c[i]:
c[i]表示当前在小路i上的分身的采摘数量。这样一来,无论什么时候挥动金箍棒,每个采摘数量都会与某个小路关联,而不是与个特定的分身关联。
定义left[i]:
接下来,为了进一步简化问题,对于每个小路i,我们定义left[i]为从小路i向左最近的有天兵把守的小路的距离。特别地,如果小路i上有天兵把守,则left[i]=0。
推导过程:
1. 初始状态:
在初始时刻,每个小路i上的分身采摘数量为c[i],且所有分身的初始采摘数量均为0。
2. 移动分析:
每次挥动金箍棒,所有分身都会向右移动一条小路。我们关心的是分身在移动过程中遇到的天兵把守的情况。
3. 采摘数量的限制:
由于left[i]表示从小路i向左最近的有天兵把守的小路的距离,因此对于任何时刻,c[i]的最大值为left[i]。这是为移动过程中,分身在遇到有天兵把守的小路之前,最多移动了left[i]次。因此,c[i]的最大值为left[i].
4. 最大采摘数量的实现:
如果我们挥动金箍棒至少left[i]次,那么可以保证c[i]达到left[i]。在最多挥动N-1次后,c[i]将会达到left[i]的最大值。
5. 结果求解:
综上所述,最终所有分身的采摘数量之和的最大可能值为所有left[i]的总和,即∑μ1left[i]。
在具体实现时,我们无需去真正地去定义c[]数组和left[]数组,而是可以从前往后遍历每一条小路,维护一个变量c来计算连续的、没有天兵把守的小路的长度,并根据求和公式2来计算 ∑N1left[i]。这点大家应该还挺好理解的。
时间复杂度为O(N)。
程序如下:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
//解决VS_2022中scanf返回值被忽略的问题
/*#include<bits/stdc++.h>_VS默认情况下并不支持 stdc++.h 这个头文件,
所以我们采用如下头文件来编译运行代码*/
//考虑到大家对这些头文件比较陌生,所以我们来详解一下这些头文件
#include <iostream>
/*#include <iostream> 是C++程序中非常常见的一条预处理指令,它包含了iostream库。
iostream库提供了C++程序中用于处理标准输入/输出流的基本功能。
这个库中定义了一些重要的类和对象,以及与输入/输出相关的函数。
以下是iostream库中的一些关键组件:
std::istream 类:这是一个输入流类,用于从输入设备(如键盘、文件等)读取数据。
std::ostream 类:这是一个输出流类,用于向输出设备(如屏幕、文件等)写入数据。
std::cin 对象:这是一个std::istream类型的对象,表示标准输入流(通常是键盘输入)。
它是std::istream类的一个实例。
std::cout 对象:这是一个std::ostream类型的对象,表示标准输出流(通常是屏幕输出)。
它是std::ostream类的一个实例。
std::cerr 对象:这是一个std::ostream类型的对象,表示标准错误流。
它通常用于输出错误消息。
与std::cout相比,std::cerr默认情况下是非缓冲的,这意味着错误消息会立即显示,而不是等待缓冲区被填满。
std::clog 对象:这是一个std::ostream类型的对象,表示标准日志流。
它与std::cerr类似,但用于输出日志消息。
除了这些基本组件之外,iostream库还包括了其他与流处理相关的类,
例如std::stringstream、std::ifstream和std::ofstream等。
这些类分别用于处理字符串流、文件输入流和文件输出流。*/
#include <vector>
/*
(1)在C++中,vector是一种使用非常广泛的容器类,属于标准模板库(STL)的一部分。
它能够存储任意类型的对象,从基本类型如int、float到自定义类型都可以。
更重要的是,vector支持动态扩展,不需要程序员手动管理内存,极大地提高了编程效率和安全性。
(2)vector是变长数组,支持随机访问,不支持在任意位置O(1)插入。为了保证效率,元素的增删一般应该在末尾进行。
(3)在C++的标准模板库(STL)中,vector是一个非常有用的动态数组容器。它允许我们存储可变大小的同类型元素序列,
并且能够动态地增长和缩小。由于其灵活性和易用性,vector在C++编程中得到了广泛的应用。
*/
#include <algorithm>
/*
#include <algorithm> 是C++中一个常用的预处理指令,它包含了algorithm库。
这个库提供了大量用于操作序列(例如数组、向量、列表等容器)的通用算法,这些算法包括查找、排序、复制、移动、修改和其他操作。
以下是algorithm库中一些常用函数接口的概述:
std::sort(begin, end):对序列进行排序。begin和end是序列的起始和结束迭代器。
std::stable_sort(begin, end):对序列进行稳定排序(保持相等元素的相对顺序)。
std::reverse(begin, end):反转序列中的元素顺序。
std::find(begin, end, value):查找序列中第一个等于value的元素。如果找到,返回指向该元素的迭代器,否则返回end。
std::count(begin, end, value):计算序列中等于value的元素个数。
std::min_element(begin, end):查找序列中的最小元素。返回指向最小元素的迭代器。
std::max_element(begin, end):查找序列中的最大元素。返回指向最大元素的迭代器。
std::copy(source_begin, source_end, dest_begin):从源序列复制元素到目标序列。返回指向目标序列末尾的迭代器。
std::move(source_begin, source_end, dest_begin):将源序列的元素移动到目标序列。
std::remove(begin, end, value):移除序列中所有等于value的元素。返回指向新序列末尾的迭代器。
std::unique(begin, end):移除序列中所有连续重复的元素。返回指向新序列末尾的迭代器。
std::replace(begin, end, old_value, new_value):将序列中所有等于old_value的元素替换为new_value。
std::fill(begin, end, value):将序列中的所有元素设置为value。
std::for_each(begin, end, function):对序列中的每个元素应用function。
这只是algorithm库中众多函数的一部分。在实际编程中,这个库为处理序列数据提供了非常强大的功能。
使用这些通用算法可以简化代码,提高代码的可读性和可维护性。
*/
using namespace std;//命名空间
int main() {
//对用户输入的一串字符进行处理;
int N; cin >> N;//声明整型变量N,用于接收用户输入的一个整数
vector<int>A(N);//使用cin从标准输入读取N并动态创建一个大小为N的vector<int类型的数组A;
for (int i = 0; i < N; i++) {//for循环;遍历范围从0~N-1;
char x;
cin >> x;
if (x == 'L') A[i] = 0;
else A[i] = 1;
}
//用于将向量A中第一个遇到的0元素移动到最前面的位置,然后终止循环
for (int i = 0; i < N; i++) {//for循环;遍历整型向量A中的元素
if (A[i] == 0) {//调用std::rotaate
rotate(A.begin(), A.begin() + i + 1, A.end());//由i+1开始到向量结尾的所有元素向前移动一位,使得原本在i处的0元素移动到向量的最前面
break;//找到并移动了第一个0,跳出循环
}
}
//用于计算给定数组A中连续为1的子段之间的空0元素的数量
long long c = 0, ans = 0;
/*long long c=0 长整型变量;用于记录遇到的第一个1之前连续的0的个数;
long long ans=0 用于存储最终结果*/
for (int i = 0; i < N; i++) {//for循环,遍历数组A的每个元素,从索引i = 0到N - 1
if (A[i] == 0) {
ans += c * (c + 1) / 2;//组合公式计算
c = 0;
}
if (A[i] == 1) c++;
}
//循环结束
cout << ans << '\n';//ans包含了所有有效空0元素的序列和,最后通过cout将其输出,并返回0表示程序正常结束。
return 0;
}