(1)《Priority Queue》
title:
多个对优先队列 S 的操作被给出。每个操作在一行中给出,格式为 "insert k"、"extract" 或 "end"。这里,k 表示要插入优先队列的整数元素。
输入以 "end" 操作结束。
对于每个 "extract" 操作,打印从优先队列 S 中提取的元素,每个元素占一行。
解题思路:
初始化最大堆:
使用priority_queue
来定义一个最大堆maxHeap
。priority_queue
默认是一个最大堆,即堆顶元素是当前堆中的最大值。
处理用户输入:
程序进入一个无限循环,等待用户输入操作指令。
用户输入的操作指令有三种可能:
insert
:插入一个整数到最大堆中。
extract
:从最大堆中提取并删除堆顶元素(即当前最大值),并输出该元素。
end
:结束程序。
插入操作:
当用户输入insert
时,程序会读取一个整数k
,并将其插入到maxHeap
中。maxHeap.push(k)
会将k
插入到堆中,并自动调整堆结构以保持最大堆的性质。
提取操作:
当用户输入extract
时,程序会检查堆是否为空。如果堆不为空,程序会提取并删除堆顶元素(即当前最大值),并输出该元素。如果堆为空,程序会输出"Heap is empty!"
。
结束程序:
当用户输入end
时,程序会跳出循环,结束运行。
无效操作处理:
如果用户输入的操作指令不是insert
、extract
或end
,程序会输出"Invalid operation!"
,提示用户输入无效。
#include
#include
#include
using namespace std;
int main() {
priority_queue maxHeap;
string operation;
int k;
while (true) {
cin >> operation;
if (operation == "insert") {
cin >> k;
maxHeap.push(k);
} else if (operation == "extract") {
if (!maxHeap.empty()) {
int maxElement = maxHeap.top();
maxHeap.pop();
cout << maxElement << endl;
} else {
cout << "Heap is empty!" << endl;
}
} else if (operation == "end") {
break;
} else {
cout << "Invalid operation!" << endl;
}
}
return 0;
}
以下省略题目内容。。。。
(2) 《ST表&&RMQ问题》
解题思路:
#include
:包含了几乎所有标准库的头文件,方便编程,但可能会增加编译时间。MAXN
:定义数组的最大长度,这里设为 1e5 + 5
,用于存储输入的数据。LOG
:由于要处理的数据规模最大为 1e5
,而 log2(1e5) ≈ 16.6
,取 20
能确保后续处理不会越界。st[MAXN][LOG]
:ST 表,用于存储区间最大值信息。st[i][j]
表示从第 i
个元素开始,长度为 2^j
的区间内的最大值。log2_[MAXN]
:预计算 log2
值的数组,避免在查询时重复计算 log2
。cin
的输入开销,提高程序的输入效率。log2
值log2(i) = log2(i/2) + 1
的性质,预先计算出 1
到 n
的 log2
值并存储在 log2_
数组中,避免在查询时使用 log
函数带来的额外开销。st[i][0]
初始化为数组 a
中第 i
个元素,因为长度为 2^0 = 1
的区间最大值就是该元素本身。j > 0
,st[i][j]
表示的区间长度为 2^j
,可以将其拆分为两个长度为 2^(j - 1)
的子区间,即 [i, i + 2^(j - 1) - 1]
和 [i + 2^(j - 1), i + 2^j - 1]
,则 st[i][j]
等于这两个子区间的最大值中的较大值。[l, r]
,找到一个最大的 k
使得 2^k <= r - l + 1
,可以通过预计算的 log2_
数组快速得到 k
。[l, r]
用两个长度为 2^k
的子区间覆盖,即 [l, l + 2^k - 1]
和 [r - 2^k + 1, r]
,这两个子区间的最大值分别存储在 st[l][k]
和 st[r - 2^k + 1][k]
中,取它们的最大值即为区间 [l, r]
的最大值。n
和查询次数 m
,以及数组 a
的元素。preprocessLog2
函数预计算 log2
值,调用 buildST
函数构建 ST 表。m
次处理查询,每次读入查询区间 [l, r]
,将其转换为 0 下标索引,调用 queryST
函数进行区间最大值查询并输出结果。#include
using namespace std;
const int MAXN = 1e5 + 5;
const int LOG = 20; // log2(1e5) ≈ 16.6,取20足够
int st[MAXN][LOG]; // ST表
int log2_[MAXN]; // 预计算log2值
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
void preprocessLog2(int n) {
log2_[1] = 0;
for (int i = 2; i <= n; i++) {
log2_[i] = log2_[i / 2] + 1;
}
}
void buildST(int n, int a[]) {
for (int i = 0; i < n; i++) {
st[i][0] = a[i]; // 初始化ST表的第一层
}
for (int j = 1; j < LOG; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int queryST(int l, int r) {
int k = log2_[r - l + 1];
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
int main() {
int n = read(), m = read();
int a[MAXN];
for (int i = 0; i < n; i++) {
a[i] = read();
}
preprocessLog2(n);
buildST(n, a);
while (m--) {
int l = read(), r = read();
l--; r--; // 转换为0-based索引
printf("%d\n", queryST(l, r));
}
return 0;
}
(3)《合并果子》
解题思路:
#include
:用于输入输出操作,如cin
和cout
。#include
:引入priority_queue
(优先队列),它是实现最小堆的关键数据结构。#include
:用于存储果子的数量,vector
是一个动态数组。using namespace std;
:使用标准命名空间,方便使用标准库中的各种类和函数。minimalEnergy
函数priority_queue, greater> minHeap;
:创建一个最小堆,greater
是一个比较函数,使得堆顶元素始终是堆中最小的元素。for
循环遍历fruits
向量,将每个果子的数量加入最小堆。while
循环,只要堆中元素数量大于 1,就继续合并操作。totalEnergy
中,并将合并后的结果重新插入最小堆。totalEnergy
。main
函数n
。n
的vector
来存储每种果子的数量,并通过for
循环读取这些数量。minimalEnergy
函数,传入果子的种类数n
和存储果子数量的向量fruits
,得到最小体力消耗result
。cout
输出最小体力消耗。#include
#include
#include
using namespace std;
int minimalEnergy(int n, vector& fruits) {
// 使用优先队列(默认是最大堆,所以需要用 greater 实现最小堆)
priority_queue, greater> minHeap;
// 将所有果子的数目加入最小堆
for (int fruit : fruits) {
minHeap.push(fruit);
}
int totalEnergy = 0;
// 当堆中至少有两个元素时,继续合并
while (minHeap.size() > 1) {
// 取出最小的两个堆
int first = minHeap.top();
minHeap.pop();
int second = minHeap.top();
minHeap.pop();
// 合并后的体力消耗
int energy = first + second;
totalEnergy += energy;
// 将合并后的堆重新放回堆中
minHeap.push(energy);
}
return totalEnergy;
}
int main() {
int n;
cin >> n; // 输入果子的种类数
vector fruits(n);
// 输入每种果子的数目
for (int i = 0; i < n; i++) {
cin >> fruits[i];
}
// 计算最小体力消耗
int result = minimalEnergy(n, fruits);
// 输出结果
cout << result << endl;
return 0;
}
(4)《约瑟夫问题》
解题思路:
#include
:用于实现输入输出功能,例如读取用户输入和输出结果。#include
:引入标准库中的 std::list
,它是一个双向链表容器,支持高效的插入和删除操作,非常适合用于模拟约瑟夫环问题中的人员出圈过程。main
函数n
和 m
,其中 n
表示总人数,m
表示报数到 m
的人出圈。std::list
类型的容器 people
来存储人员编号。for
循环将从 1 到 n
的整数依次添加到 people
列表中,代表 n
个人的编号。auto
关键字自动推导类型,将迭代器 it
初始化为指向 people
列表的第一个元素,用于后续遍历列表。while
循环,只要 people
列表不为空,就继续模拟出圈过程。for
循环从 1 到 m - 1
进行计数。在计数过程中,如果迭代器 it
到达列表末尾,将其重置为列表开头,模拟循环报数。每次计数后,将迭代器 it
向后移动一位。m
时,输出当前迭代器指向的人员编号。如果列表中还有其他人员,在输出编号后输出一个空格。people.erase(it)
删除当前迭代器指向的元素,并将迭代器更新为删除元素之后的元素。如果删除后迭代器指向列表末尾,将其重置为列表开头。#include
#include
int main() {
int n, m;
std::cin >> n >> m;
std::list people;
for (int i = 1; i <= n; ++i) {
people.push_back(i);
}
auto it = people.begin();
while (!people.empty()) {
for (int i = 1; i < m; ++i) {
if (it == people.end()) {
it = people.begin();
}
++it;
if (it == people.end()) {
it = people.begin();
}
}
std::cout << *it;
if (people.size() > 1) {
std::cout << " ";
}
it = people.erase(it);
if (it == people.end()) {
it = people.begin();
}
}
return 0;
}
学习总结:通过本次学习,了解了栈、队列、堆、st表的含义,以及在题目中的应用。