ACM-day3

 Priority Queue

优先队列是一种数据结构,它维护一组元素 SS,每个元素都有一个关联的值(键),并支持以下操作:

  • insert(S,k)insert(S,k):将一个元素 kk 插入到集合 SS 中
  • extractMax(S)extractMax(S):移除并返回具有最大键的 SS 元素

编写一个程序,对优先队列 SS 执行 insert(S,k)insert(S,k) 和 extractMax(S)extractMax(S) 操作。 优先队列管理一组整数,这些整数也是优先级的键。

输入

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

输入以 "end" 操作结束。

输出

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

约束

  • 操作的数量 ≤2,000,000≤2,000,000
  • 0≤k≤2,000,000,0000≤k≤2,000,000,000
InputcopyOutputcopy
insert 8
insert 2
extract
insert 10
extract
insert 11
extract
extract
end
8
10
11
2

解题思路

这是一道经典的优先队列题,要求我们将数依次入队,然后输出当前的最大值,利用优先队列可以轻松解决,只要判断要求的操作,如果是inser就入队,是extract就将当前最大值出队,是end就结束程序。

代码

#include <iostream>
#include <queue>
#include <string>

using namespace std;

int main() {
    priority_queue<int> pq;  // 默认是最大堆
    string operation;
    int k;

    while (cin >> operation) {
        if (operation == "insert") {
            cin >> k;
            pq.push(k);
        } else if (operation == "extract") {
            if (!pq.empty()) {
                cout << pq.top() << endl;
                pq.pop();
            }
        } else if (operation == "end") {
            break;
        }
    }

    return 0;
}

 ST 表 && RMQ 问题

 

Background

这是一道 ST 表经典题——静态区间最大值

请注意最大数据时限只有 0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O(1)O(1)。若使用更高时间复杂度算法不保证能通过。

如果您认为您的代码时间复杂度正确但是 TLE,可以尝试使用快速读入:

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-48;ch=getchar();}
	return x*f;
}

Copy

C++

函数返回值为读入的第一个整数。

快速读入作用仅为加快读入,并非强制使用。

Description

给定一个长度为 NN 的数列,和 MM 次询问,求出每一次询问的区间内数字的最大值。

Input

第一行包含两个整数 N,MN,M,分别表示数列的长度和询问的个数。

第二行包含 NN 个整数(记为 aiai​),依次表示数列的第 ii 项。

接下来 MM 行,每行包含两个整数 li,rili​,ri​,表示查询的区间为 [li,ri][li​,ri​]。

Output

输出包含 MM 行,每行一个整数,依次表示每一次询问的结果。

Sample 1

InputcopyOutputcopy
8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8
9
9
7
7
9
8
7
9

Hint

对于 30%30% 的数据,满足 1≤N,M≤101≤N,M≤10。

对于 70%70% 的数据,满足 1≤N,M≤1051≤N,M≤105。

对于 100%100% 的数据,满足 1≤N≤1051≤N≤105,1≤M≤2×1061≤M≤2×106,ai∈[0,109]ai​∈[0,109],1≤li≤ri≤N1≤li​≤ri​≤N。

解题思路

基于稀疏表(Sparse Table)的区间最大值查询算法。通过快速读入函数读取数组大小 N 和查询次数 M,并构建稀疏表 st,其中 st[i][j] 表示从位置 i 开始、长度为 2^j 的区间内的最大值。稀疏表通过动态规划的方式预处理,使得每次查询能够在 O(1) 时间内通过比较两个重叠区间的结果得到区间 [l, r] 的最大值。最终,代码高效处理 M 次查询并输出结果。

代码

#include <iostream>
#include <vector>
#include <cstdio> // 添加此行以包含 getchar 的声明
#include <cmath> // 添加此行以包含 log2 的声明

using namespace std;

// 快速读入函数
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 - 48; ch = getchar(); }
    return x * f;
}

int main() {
    int N = read(), M = read();
    const int K = log2(N) + 1; // 假设 K 是 N 的对数加一
    vector<int> a(N); // 假设 a 是一个大小为 N 的向量
    vector<vector<int> > st(N, vector<int>(K)); 

    // 从输入中读取数据填充 a
    for (int i = 0; i < N; ++i) {
        a[i] = read();
    }

    // 构建稀疏表
    for (int i = 0; i < N; ++i) {
        st[i][0] = a[i];
    }

    for (int j = 1; (1 << j) <= N; ++j) {
        for (int i = 0; i + (1 << j) - 1 < N; ++i) {
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }

    // 处理查询
    for (int q = 0; q < M; ++q) {
        int l = read() - 1, r = read() - 1; // 转换为0索引
        int j = log2(r - l + 1);
        int max_val = max(st[l][j], st[r - (1 << j) + 1][j]);
        cout << max_val << "\n"; 
    }

    return 0;
}

 

 合并果子

 洛谷 - P1090 

Description

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n−1n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 11 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 33 种果子,数目依次为 11 , 22 , 99 。可以先将 11 、 22 堆合并,新堆数目为 33 ,耗费体力为 33 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 1212 ,耗费体力为 1212 。所以多多总共耗费体力 =3+12=15=3+12=15 。可以证明 1515 为最小的体力耗费值。

Input

共两行。
第一行是一个整数 n(1≤n≤10000)n(1≤n≤10000) ,表示果子的种类数。

第二行包含 nn 个整数,用空格分隔,第 ii 个整数 ai(1≤ai≤20000)ai​(1≤ai​≤20000) 是第 ii 种果子的数目。

Output

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 231231 。

Sample 1

InputcopyOutputcopy
3 
1 2 9 
15

Hint

对于 30%30% 的数据,保证有 n≤1000n≤1000:

对于 50%50% 的数据,保证有 n≤5000n≤5000;

对于全部的数据,保证有 n≤10000n≤10000。

解题思路

通过使用最小堆(优先队列),每次从堆中取出两个最小的果子堆合并,并将合并后的果子堆重新插入堆中,同时累加体力耗费。最终,当堆中只剩下一堆时,输出总的体力耗费值。该算法利用贪心策略,确保每次合并的体力耗费最小,时间复杂度为 O(nlog⁡n)O(nlogn)。

代码

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> fruits(n);
    for (int i = 0; i < n; ++i) {
        cin >> fruits[i];
    }

    // 使用优先队列(最小堆)
    priority_queue<int, vector<int>, greater<int> > minHeap(fruits.begin(), fruits.end());

    int totalCost = 0;
    while (minHeap.size() > 1) {
        // 取出两个最小的元素
        int first = minHeap.top();
        minHeap.pop();
        int second = minHeap.top();
        minHeap.pop();

        // 合并这两个元素并计算体力耗费
        int cost = first + second;
        totalCost += cost;

        // 将合并后的元素重新插入堆中
        minHeap.push(cost);
    }

    cout << totalCost << endl;
    return 0;
}

 约瑟夫问题

 洛谷 - P1996 

Description

nn 个人围成一圈,从第一个人开始报数,数到 mm 的人出列,再由下一个人重新从 11 开始报数,数到 mm 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

注意:本题和《深入浅出-基础篇》上例题的表述稍有不同。书上表述是给出淘汰 n−1n−1 名小朋友,而该题是全部出圈。

Input

输入两个整数 n,mn,m。

Output

输出一行 nn 个整数,按顺序输出每个出圈人的编号。

Sample 1

InputcopyOutputcopy
10 3
3 6 9 2 7 1 8 5 10 4

Hint

1≤m,n≤1001≤m,n≤100

解题思路

给定 n 个人围成一圈,从第一个人开始报数,每次数到 m 的人出列,直到所有人都出列。代码使用队列 q 存储人员编号,通过循环将前 m-1 个人移动到队列末尾,然后输出第 m 个人并将其移除队列,直到队列为空。最终输出按出列顺序排列的人员编号。

代码

#include <iostream>
#include <queue>

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;

    queue<int> q;
    for (int i = 1; i <= n; ++i) {
        q.push(i);
    }

    while (!q.empty()) {
        for (int i = 1; i < m; ++i) {
            q.push(q.front());
            q.pop();
        }
        cout << q.front() << " ";
        q.pop();
    }

    return 0;
}

 Look Up S

 洛谷 - P2947 

Description

Farmer John’s N (1 <= N <= 100,000) cows, conveniently numbered 1…N, are once again standing in a row. Cow i has height H_i (1 <= H_i <= 1,000,000).

Each cow is looking to her left toward those with higher index numbers. We say that cow i ‘looks up’ to cow j if i < j and H_i < H_j. For each cow i, FJ would like to know the index of the first cow in line looked up to by cow i.

Note: about 50% of the test data will have N <= 1,000.

约翰的 N(1≤N≤105)N(1≤N≤105) 头奶牛站成一排,奶牛 ii 的身高是 Hi(1≤Hi≤106)Hi​(1≤Hi​≤106)。现在,每只奶牛都在向右看齐。对于奶牛 ii,如果奶牛 jj 满足 i<ji<j 且 Hi<HjHi​<Hj​,我们可以说奶牛 ii 可以仰望奶牛 jj。 求出每只奶牛离她最近的仰望对象。

Input

Input

  1. * Line 1: A single integer: N

* Lines 2…N+1: Line i+1 contains the single integer: H_i

第 11 行输入 NN,之后每行输入一个身高 HiHi​。

Output

* Lines 1…N: Line i contains a single integer representing the smallest index of a cow up to which cow i looks. If no such cow exists, print 0.

共 NN 行,按顺序每行输出一只奶牛的最近仰望对象,如果没有仰望对象,输出 00。

Sample 1

InputcopyOutputcopy
6 
3 
2 
6 
1 
1 
2 
3 
3 
0 
6 
6 
0 

Hint

FJ has six cows of heights 3, 2, 6, 1, 1, and 2.

Cows 1 and 2 both look up to cow 3; cows 4 and 5 both look up to cow 6; and cows 3 and 6 do not look up to any cow.

【输入说明】66 头奶牛的身高分别为 3,2,6,1,1,2。

【输出说明】奶牛 #1,#2 仰望奶牛 #3,奶牛 #4,#5 仰望奶牛 #6,奶牛 #3 和 #6 没有仰望对象。

【数据规模】

对于 20%20% 的数据:1≤N≤101≤N≤10;

对于 50%50% 的数据:1≤N≤1031≤N≤103;

对于 100%100% 的数据:1≤N≤105,1≤Hi≤1061≤N≤105,1≤Hi​≤106。

解题思路

  1. 通过从右往左遍历奶牛的身高数组,并利用栈存储奶牛的索引,确保栈中存储的奶牛身高是单调递减的。
  2. 对于每个奶牛,判断栈顶奶牛是否是它右边第一个比它矮的奶牛。如果栈顶奶牛身高不大于当前奶牛,则弹出栈顶,直到找到比当前奶牛矮的奶牛为止。
  3. 若栈不为空,栈顶即为当前奶牛的仰望对象的索引;否则,结果为 0。
  4. 最后输出每个奶牛的仰望对象的索引。

代码

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

int main() {
    int N;
    cin >> N;
    vector<int> H(N + 1); // 奶牛的身高,索引从 1 开始
    for (int i = 1; i <= N; ++i) {
        cin >> H[i];
    }

    vector<int> result(N + 1, 0); // 结果数组,初始化为 0
    stack<int> st; // 单调栈,存储奶牛的索引

    // 从右往左遍历
    for (int i = N; i >= 1; --i) {
        // 如果栈不为空且当前奶牛的身高大于栈顶奶牛的身高
        while (!st.empty() && H[i] >= H[st.top()]) {
            st.pop(); // 弹出栈顶奶牛
        }
        // 如果栈不为空,说明栈顶奶牛是当前奶牛的仰望对象
        if (!st.empty()) {
            result[i] = st.top();
        }
        // 将当前奶牛的索引入栈
        st.push(i);
    }

    // 输出结果
    for (int i = 1; i <= N; ++i) {
        cout << result[i] << endl;
    }

    return 0;
}

 国旗计划

 洛谷 - P4155 

Description

A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 NN 名优秀的边防战士作为这项计划的候选人。

A 国幅员辽阔,边境线上设有 MM 个边防站,顺时针编号 11 至 MM。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。NN 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。

现在,国土安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。

Input

第一行,包含两个正整数 N,MN,M,分别表示边防战士数量和边防站数量。

随后 NN 行,每行包含两个正整数。其中第 ii 行包含的两个正整数 CiCi​、DiDi​ 分别表示 ii 号边防战士常驻的两个边防站编号,CiCi​ 号边防站沿顺时针方向至 DiDi​ 号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。

Output

输出数据仅 11 行,需要包含 NN 个正整数。其中,第 jj 个正整数表示 jj 号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。

Sample 1

InputcopyOutputcopy
4 8
2 5
4 7
6 1
7 3
3 3 4 3

Hint

N⩽2×105,M<109,1⩽Ci,Di⩽MN⩽2×105,M<109,1⩽Ci​,Di​⩽M。

解题思路

  1. 读取区间并调整跨越周期的区间;
  2. 使用动态规划和二进制跳跃法处理区间覆盖关系,构建一个dp表来存储区间的覆盖信息;
  3. 根据预处理结果计算每个区间所需的最少战士数;
  4. 最后输出每个区间的结果。

代码
 

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int MAX_N = 200010;
int n, m;
int dp[2 * MAX_N][25], result[MAX_N];

struct Interval {
    int id, start, end;
};

vector<Interval> intervals(2 * MAX_N);

// 自定义排序函数,根据区间的结束位置排序
bool compareIntervals(const Interval &a, const Interval &b) {
    return a.end < b.end;
}

// 预处理区间覆盖情况,使用动态规划
void preprocess() {
    int ptr = 1;
    for (int i = 1; i <= 2 * n; i++) {
        while (ptr <= 2 * n && intervals[ptr].start <= intervals[i].end) {
            ptr++;
        }
        ptr--;
        dp[i][0] = ptr;
    }

    for (int j = 1; (1 << j) <= 2 * n; j++) {
        for (int i = 1; i <= 2 * n; i++) {
            if (dp[i][j - 1]) {
                dp[i][j] = dp[dp[i][j - 1]][j - 1];
            }
        }
    }
}

int main() {
    cin >> n >> m;

    // 读取区间,并处理跨越周期的区间
    for (int i = 1; i <= n; i++) {
        int start, end;
        cin >> start >> end;
        if (end < start) {
            end += m;
        }
        intervals[i].start = start;
        intervals[i].end = end;
        intervals[i].id = i;
    }

    // 对区间按结束位置排序
    sort(intervals.begin() + 1, intervals.begin() + n + 1, compareIntervals);

    // 复制区间并调整位置,防止跨越边界
    for (int i = 1; i <= n; i++) {
        intervals[i + n].id = 0;
        intervals[i + n].start = intervals[i].start + m;
        intervals[i + n].end = intervals[i].end + m;
    }

    // 进行预处理
    preprocess();

    // 计算每个区间需要的最少战士数
    for (int i = 1; i <= n; i++) {
        int current = i, count = 1;
        for (int j = 20; j >= 0; j--) {
            if (dp[current][j] > 0 && intervals[dp[current][j]].end - intervals[i].start < m) {
                current = dp[current][j];
                count += (1 << j);
            }
        }
        result[intervals[i].id] = count + 1;
    }

    // 输出结果
    for (int i = 1; i <= n; i++) {
        cout << result[i] << " ";
    }
    cout << endl;

    return 0;
}


学习体会

通过学习,我深刻理解了栈的后进先出(LIFO)特性,掌握了栈的基本操作(如push、pop、top),并认识到栈在解决递归问题和括号匹配等问题中的重要作用。同时,我还了解了队列的先进先出(FIFO)特性,及其在广度优先搜索中的应用。此外,我掌握了优先队列通过堆实现高效元素排序和访问的方法,并深入理解了ST表在区间查询问题中的高效预处理与查询技巧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值