数据结构与算法学习笔记----贪心区间问题

数据结构与算法学习笔记----贪心区间问题

@@ author: 明月清了个风

@@ first publish time: 2025.4.3

ps⭐️一个月没更了,三月初出去玩了,然后大论文写到现在哈哈哈

贪心问题其实不像其他问题能够有非常具体的解决思路,往往是通过尝试找到一种方法后再证明其正确性,贪心的关键在于关注最优子结构向最优全局解的证明。

Acwing 905. 区间选点

原题链接\]([905. 区间选点 - AcWing题库](https://www.acwing.com/problem/content/907/)) 给定 N N N个闭区间 \[ a i , b i \] \[a_i, b_i\] \[ai,bi\],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 #### 输入格式 第一行包含整数 N N N,表示区间数。 接下来 N N N行,每行包含两个整数 a i , b i a_i, b_i ai,bi,表示一个区间的两个端点。 #### 输出格式 输出一个整数,表示所需的点的最小数量。 #### 数据范围 1 ≤ N ≤ 1 0 5 1 \\le N \\le 10\^5 1≤N≤105, − 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10\^9 \\le a_i \\le b_i \\le 10\^9 −109≤ai≤bi≤109 #### 思路 这里先直接给出结论: 1. 将所有区间按右端点从小到大排序。 2. 从前往后依次枚举每个区间,如果当前区间中已经包含点,则不选择;若不包含点,则选择当前区间的右端点。 接下来证明此策略的正确性: 因为我们要选择尽可能少的点,因此目标也就是使**选择的每个点覆盖尽可能多的区间**。 * 首先进行贪心选择的正确性证明,也就是**为什么选择右端点?** 对于第一个区间 \[ a 1 , b 1 \] \[a_1,b_1\] \[a1,b1\]来说,其右端点 b 1 b_1 b1经过排序后为所有区间的右端点中的最小值,那么剩余区间就只有两种情况,一是与 \[ a 1 , b 1 \] \[a_1,b_1\] \[a1,b1\]有交集,也就是 a i \< b 1 a_i \< b_1 ai\ b 1 a_i \> b_1 ai\>b1。那么对于第一种情况,当我们选择了右端点 b 1 b_1 b1后,该区间一定包含 b 1 b_1 b1,所以不用选择点;对于第二种情况,该区间不包含 b 1 b_1 b1,一定会另选点。也就是说,当我们选择了第一个区间的右端点 b 1 b_1 b1后,我们就覆盖了所有左端点 a i \< b 1 a_i \< b_1 ai\ #include #include #include using namespace std; const int N = 100010; int n; struct Range { int l, r; bool operator < (const Range &W)const { return r < W.r; } }range[N]; int main() { cin >> n; for(int i = 0; i < n; i ++) { int l, r; cin >> l >> r; range[i] = {l, r}; } sort(range, range + n); int res = 0, ed = -2e9; for(int i = 0; i < n; i++) { if(range[i].l > ed) { res ++; ed = range[i].r; } } cout << res << endl; return 0; } ``` ### Acwing 908. 最大不相交区间数量 \[原题链接\]([908. 最大不相交区间数量 - AcWing题库](https://www.acwing.com/problem/content/910/)) 给定 N N N个闭区间 \[ a i , b i \] \[a_i, b_i\] \[ai,bi\],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。 输出可选取区间的最大数量。 #### 输入格式 第一行包含整数 N N N,表示区间数。 接下来 N N N行,每行包含两个整数 a i , b i a_i, b_i ai,bi,表示一个区间的两个端点。 #### 输出格式 输出一个整数,表示可选取区间的最大数量。 #### 数据范围 1 ≤ N ≤ 1 0 5 1 \\le N \\le 10\^5 1≤N≤105, − 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10\^9 \\le a_i \\le b_i \\le 10\^9 −109≤ai≤bi≤109 #### 思路 直接说结论吧这一题,做法和上一题完全一致,回顾一下上一题的思路其实也能看出来。 同样可以从两个方向来证明, 根据上一题选法,选择的相邻的两个点一定是没有交集的两个区间的点,因此就可以认为我们的答案 a n s ans ans一定是一种可行方案,且 a n s ≥ c n t ans \\ge cnt ans≥cnt。 下一步证明 a n s ≤ c n t ans \\le cnt ans≤cnt,这里可以用反证法,假设我们的答案 a n s ans ans是大于 c n t cnt cnt的,也就意味着可以选择更多的存在于没有交集的区间的点,但是根据我们的选法来看,该选法覆盖了所有的区间,不存在额外的区间中的点,因此得证。 代码和上一题一样。 #### 代码 ```cpp #include #include #include #include using namespace std; const int N = 100010; int n; struct Range { int l, r; bool operator < (const Range &W)const { return r < W.r; } }range[N]; int main() { cin >> n; for(int i = 0; i < n; i ++) { int l, r; cin >> l >> r; range[i] = {l, r}; } sort(range, range + n); int res = 0, ed = -2e9; for(int i = 0; i < n; i++) { if(range[i].l > ed) { res ++; ed = range[i].r; } } cout << res << endl; return 0; } ``` ### Acwing 906. 区间分组 \[原题链接\]([906. 区间分组 - AcWing题库](https://www.acwing.com/problem/content/908/)) 给定 N N N个闭区间 \[ a i , b i \] \[a_i, b_i\] \[ai,bi\],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。 输出最小组数。 #### 输入格式 第一行包含整数 N N N,表示区间数。 接下来 N N N行,每行包含两个整数 a i , b i a_i, b_i ai,bi,表示一个区间的两个端点。 #### 输出格式 输出一个整数,表示最小组数。 #### 数据范围 1 ≤ N ≤ 1 0 5 1 \\le N \\le 10\^5 1≤N≤105, − 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10\^9 \\le a_i \\le b_i \\le 10\^9 −109≤ai≤bi≤109 #### 思路 同样直接给出思路,因为是经典的模型。 1. 将所有区间按左端点从小到大排序 2. 从前往后处理每个区间,判断能否放在某个组里,也就是判断当前区间的左端点是否大于该组中的最大的右端点 3. 如果上一步中未能找到一个组,那么就开一个新的组。 下一步就是证明该做法的正确性,首先证明我们的算法得到的 c n t cnt cnt是否大于等于正确答案 a n s ans ans,因为选取的规则保证了能够按照题意得到结果,因此肯定是一种合法方案,因此 c n t ≥ a n s cnt \\ge ans cnt≥ans肯定成立 而对于 c n t ≤ a n s cnt \\le ans cnt≤ans可以从特殊时间节点来看,也就是创建第 c n t cnt cnt个组的时刻,当我们新开第 c n t cnt cnt个组时,根据选法来看,保证了前 c n t − 1 cnt - 1 cnt−1个组的最大右端点值一定大于当前这个区间的右端点,因此无法放入任何一个组,进而新开了第 c n t cnt cnt个组,这也意味着这 c n t cnt cnt个组的第一个区间一定是相互之间有交集的,也就是新开到第 c n t cnt cnt个组的这个区间的左端点一定被包含在了前 c n t cnt cnt个组里,因此得证。 还有一个问题就是,如果存在多个能够放入的组应该放在哪一个,会不会影响最终的结果,答案是不会,任取一个就行,体现在代码中就是选最小的组。这个问题的核心就是存在多个可用组的情况下,为什么选择任意一个得到的答案都是最小的,这是因为答案其实就是所有区间的最大重叠数,也就是最多的存在同一点的区间数。假设这个数是 k k k,那么说明有 k k k个区间存在相交点,那么就至少需要 k k k组才能容纳这 k k k个组,而剩余区间的重叠数一定小于等于 k k k,因此可以分别分到这 k k k个组中。 #### 代码 ```cpp #include #include #include using namespace std; const int N = 100010; int n; struct Range { int l, r; bool operator< (const Range &W)const { return l < W.l; } }range[N]; int main() { scanf("%d", &n); for(int i = 0; i < n; i ++) { int l, r; cin >> l >> r; range[i] = {l, r}; } sort(range, range + n); priority_queue, greater> heap; for(int i = 0; i < n; i ++) { auto r = range[i]; if(heap.empty() || heap.top() >= r.l) heap.push(r.r); else { int t = heap.top(); heap.pop(); heap.push(r.r); } } printf("%d\n", heap.size()); return 0; } ``` ### Acwing 907. 区间覆盖 \[原题链接\]([907. 区间覆盖 - AcWing题库](https://www.acwing.com/problem/content/909/)) 给定 N N N个闭区间 \[ a i , b i \] \[a_i, b_i\] \[ai,bi\]以及一个区间 \[ s , t \] \[s,t\] \[s,t\],请你选择尽量少的区间,将指定区间完全覆盖。 输出最少区间数,如果无法完全覆盖则输出 − 1 -1 −1。 #### 输入格式 第一行包含两个整数 s s s和 t t t,表示给定区间的两个端点。 第二行包含整数 N N N,表示给定区间数。 接下来 N N N行,每行包含两个整数 a i , b i a_i, b_i ai,bi,表示一个区间的两个端点。 #### 输出格式 输出一个整数,表示所需最少区间数。 如果无解,则输出 − 1 -1 −1。 #### 数据范围 1 ≤ N ≤ 1 0 5 1 \\le N \\le 10\^5 1≤N≤105, − 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10\^9 \\le a_i \\le b_i \\le 10\^9 −109≤ai≤bi≤109, − 1 0 9 ≤ s ≤ t ≤ 1 0 9 -10\^9 \\le s \\le t \\le 10\^9 −109≤s≤t≤109 #### 思路 同样地给出思路: 1. 将所有区间按左端点从小到大排序 2. 从前往后枚举每个区间,在所有能覆盖 s t st st的区间中选择右端点最大的区间,然后将 s t st st更新为这个值 这个思路很容易证明是正确的,首先选法一定是合法的,并且保证了每一步的覆盖范围最大,且当前步骤的选取不会影响后续的选择,因为已经排过序了。 #### 代码 ```cpp #include #include #include #include using namespace std; const int N = 100010; int n; struct Range { int l, r; bool operator< (const Range &W)const { return l < W.l; } }range[N]; int main() { int st, ed; scanf("%d%d", &st, &ed); scanf("%d", &n); for(int i = 0; i < n; i ++) { int l,r ; cin >> l >> r; range[i] = {l, r}; } sort(range, range + n); int res = 0; bool success = false; for(int i = 0; i < n; i ++) { int j = i, r = -2e9; while(j < n && range[j].l <= st) { r = max(r, range[j].r); j ++; } if(r < st) { res = -1; break; } res ++; if(r >= ed) { success = true; break; } st = r; i = j - 1; } if(!success) res = -1; cout << res << endl; return 0; } ```

相关推荐
·醉挽清风·6 分钟前
学习笔记—C++—入门基础()
c语言·开发语言·c++·笔记·学习·算法
Kratzdisteln8 分钟前
浙考!【触发器逻辑方程推导(电位运算)】
经验分享·笔记·学习方法·高考
知识分享小能手15 分钟前
CSS3学习教程,从入门到精通, 化妆品网站 HTML5 + CSS3 完整项目(26)
前端·javascript·css·学习·css3·html5·媒体
百渡ovO23 分钟前
【蓝桥杯】每日练习 Day21
c++·算法·蓝桥杯
沙子可可38 分钟前
深入学习Pytorch:第一章-初步认知
人工智能·pytorch·深度学习·学习
一捌年1 小时前
排序算法-插入排序
数据结构·算法·排序算法
灿烂的贝壳1 小时前
【算法实践】算法面试常见问题——数组的波浪排序
python·算法·排序算法·波浪序
_extraordinary_1 小时前
简单多状态dp问题 + 总结(一)
算法·dp
能来帮帮蒟蒻吗1 小时前
GO语言学习(17)Gorm的数据库操作
开发语言·学习·golang
李匠20241 小时前
C++学习之LINUX网络编程-套接字通信基础
c++·学习