学习总结3

(1)《Priority Queue》

title:

输入

多个对优先队列 S 的操作被给出。每个操作在一行中给出,格式为 "insert k"、"extract" 或 "end"。这里,k 表示要插入优先队列的整数元素。

输入以 "end" 操作结束。

输出

对于每个 "extract" 操作,打印从优先队列 S 中提取的元素,每个元素占一行。

约束

  • 操作的数量 ≤2,000,000
  • 0≤k≤2,000,000,000

解题思路:

  1. 初始化最大堆

    • 使用priority_queue来定义一个最大堆maxHeappriority_queue默认是一个最大堆,即堆顶元素是当前堆中的最大值。

  2. 处理用户输入

    • 程序进入一个无限循环,等待用户输入操作指令。

    • 用户输入的操作指令有三种可能:

      • insert:插入一个整数到最大堆中。

      • extract:从最大堆中提取并删除堆顶元素(即当前最大值),并输出该元素。

      • end:结束程序。

  3. 插入操作

    • 当用户输入insert时,程序会读取一个整数k,并将其插入到maxHeap中。maxHeap.push(k)会将k插入到堆中,并自动调整堆结构以保持最大堆的性质。

  4. 提取操作

    • 当用户输入extract时,程序会检查堆是否为空。如果堆不为空,程序会提取并删除堆顶元素(即当前最大值),并输出该元素。如果堆为空,程序会输出"Heap is empty!"

  5. 结束程序

    • 当用户输入end时,程序会跳出循环,结束运行。

  6. 无效操作处理

    • 如果用户输入的操作指令不是insertextractend,程序会输出"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问题》

解题思路:

1. 头文件和常量定义

  • #include :包含了几乎所有标准库的头文件,方便编程,但可能会增加编译时间。
  • MAXN:定义数组的最大长度,这里设为 1e5 + 5,用于存储输入的数据。
  • LOG:由于要处理的数据规模最大为 1e5,而 log2(1e5) ≈ 16.6,取 20 能确保后续处理不会越界。

2. 数组定义

  • st[MAXN][LOG]:ST 表,用于存储区间最大值信息。st[i][j] 表示从第 i 个元素开始,长度为 2^j 的区间内的最大值。
  • log2_[MAXN]:预计算 log2 值的数组,避免在查询时重复计算 log2

3. 快速读入函数

  • 该函数通过字符读取的方式快速读入整数,避免了 cin 的输入开销,提高程序的输入效率。

4. 预计算 log2 值

  • 利用 log2(i) = log2(i/2) + 1 的性质,预先计算出 1 到 n 的 log2 值并存储在 log2_ 数组中,避免在查询时使用 log 函数带来的额外开销。

5. 构建 ST 表

  • 初始化:将 st[i][0] 初始化为数组 a 中第 i 个元素,因为长度为 2^0 = 1 的区间最大值就是该元素本身。
  • 递推构建:对于 j > 0st[i][j] 表示的区间长度为 2^j,可以将其拆分为两个长度为 2^(j - 1) 的子区间,即 [i, i + 2^(j - 1) - 1] 和 [i + 2^(j - 1), i + 2^j - 1],则 st[i][j] 等于这两个子区间的最大值中的较大值。

6. 区间查询

  • 对于查询区间 [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] 的最大值。

7. 主函数

  • 读入数组长度 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)《合并果子》

解题思路:

1. 头文件和命名空间
  • #include :用于输入输出操作,如cincout
  • #include :引入priority_queue(优先队列),它是实现最小堆的关键数据结构。
  • #include :用于存储果子的数量,vector是一个动态数组。
  • using namespace std;:使用标准命名空间,方便使用标准库中的各种类和函数。
2. minimalEnergy函数
  • 最小堆的创建
    • priority_queue, greater> minHeap;:创建一个最小堆,greater 是一个比较函数,使得堆顶元素始终是堆中最小的元素。
  • 初始化最小堆
    • 通过for循环遍历fruits向量,将每个果子的数量加入最小堆。
  • 合并过程
    • 使用while循环,只要堆中元素数量大于 1,就继续合并操作。
    • 每次从堆中取出最小的两个元素(即堆顶元素),将它们合并,合并的体力消耗为这两个元素之和。
    • 将合并后的体力消耗累加到totalEnergy中,并将合并后的结果重新插入最小堆。
  • 返回结果
    • 当堆中只剩下一个元素时,合并过程结束,返回总的体力消耗totalEnergy
3. main函数
  • 输入处理
    • 首先读取果子的种类数n
    • 创建一个大小为nvector来存储每种果子的数量,并通过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)《约瑟夫问题》

解题思路:

1. 头文件和命名空间
  • #include :用于实现输入输出功能,例如读取用户输入和输出结果。
  • #include :引入标准库中的 std::list,它是一个双向链表容器,支持高效的插入和删除操作,非常适合用于模拟约瑟夫环问题中的人员出圈过程。
2. 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表的含义,以及在题目中的应用。

你可能感兴趣的:(学习,java,前端)