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

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

@@ 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; } ```

相关推荐
醉暮天34 分钟前
4.25学习——文件上传之00截断
学习
blackA_4 小时前
数据库MySQL学习——day4(更多查询操作与更新数据)
数据库·学习·mysql
ShiinaMashirol4 小时前
代码随想录打卡|Day27(合并区间、单调递增的数字、监控二叉树)
java·算法
梁下轻语的秋缘6 小时前
每日c/c++题 备战蓝桥杯(P1049 [NOIP 2001 普及组] 装箱问题)
c语言·c++·学习·蓝桥杯
wuqingshun3141596 小时前
蓝桥杯 5. 交换瓶子
数据结构·c++·算法·职场和发展·蓝桥杯
Demons_kirit6 小时前
Leetcode 2845 题解
算法·leetcode·职场和发展
刘婉晴6 小时前
【信息安全工程师备考笔记】第三章 密码学基本理论
笔记·安全·密码学
球求了6 小时前
C++:继承机制详解
开发语言·c++·学习
adam_life7 小时前
http://noi.openjudge.cn/——2.5基本算法之搜索——200:Solitaire
算法·宽搜·布局唯一码
时光追逐者7 小时前
MongoDB从入门到实战之MongoDB快速入门(附带学习路线图)
数据库·学习·mongodb