目录
1.贪心算法的思想
2.区间贪心算法常用的一些题目类型
1.选择最多不相交区间问题
P2970 [USACO09DEC] Selfish Grazing S
1.思路分析
2.上代码
2.区间选点问题
P1250 种树
1.题目
2.方法一
1.代码解释
3.方法二
3.区间合并问题
P2434 [SDOI2005] 区间
1. 思路分析
2.上代码
4.区间覆盖问题
P1668 [USACO04DEC] Cleaning Shifts S
1.思路分析
2.代码解释
5.区间分组
T471772 cici排课
编辑 1.一点点思路
2.代码实现与算法思路
3.举一反三
3.遇到了(区间)贪心的题我们应该怎么做
end👍🏻⭐ok?
1.贪心算法的思想
贪心算法是从问题的初始状态出发,通过若干次的贪心选择而得到的最优值(或较优值)的1种求解问题策略,即贪心策略。
2.区间贪心算法常用的一些题目类型
1.选择最多不相交区间问题
P2970 [USACO09DEC] Selfish Grazing S
洛谷:P2970 [USACO09DEC] Selfish Grazing S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P2970
1.思路分析
题题目的大概意思就是给定我们N个区间,求最多不相交区间有多少个。
我们可以按照区间的右端点从小到大排序
然后我们创建一个变量命名为j和一个变量cnt(计数),再次循环整个结构体(输入的那些区间,已经排序完), 如果循环到的这个区间的左端点在标签变量j的右边,把j更新为这个区间的左端点,cnt++
2.上代码
#include <bits/stdc++.h>
using namespace std;
struct M{
int s,e;
}a[500005];
bool cmp(M x,M y){
return x.e <y.e;
}
int main(){
int n;
cin >> n;
for(int i =0; i < n; i++){
cin >> a[i].s >> a[i].e;
}
sort(a,a+n,cmp);
int j = -1,cnt = 0;
for (int i =0; i < n; i++){
if (a[i].s >= j){
j = a[i].e;
cnt ++;
}
}
cout << cnt;
return 0;
}
2.区间选点问题
P1250 种树
洛谷:
P1250 种树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1250
1.题目
2.方法一
把所有树都往右边种,为了让他们重叠区间的更多,而且重叠的部分大部分都在右侧,我们就要让这些区间按右端点,从小到大排序,下图是我列举的样例。
首先先把样例归一整一下(排序)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
【 | | | | | 】 | |||||
【 | | | 】 | ||||||
【 | | | 】 | ||||||
【 | 】 |
从第一个开始在尾巴上种树,如果这个区间内已经种到了t[i]棵的话就不种了 , 如果没有种到这么多棵树那就得从后往前,循环种树只要种到为止.(括号代表已经种上树了,+1代表多种一棵树)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
【 | | | (|)+1 | (】)+1 | |||||
(【) | (|) | (】)被底下的人种上了 | ||||||
(【) | (|)+1 | 】 | ||||||
(【)+1 | (】)+1 |
其实这样的话更形象,就是有点乱:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
([) | | | (|[) | (]|[) | (]|) | ] | ([) | (]) |
同样颜色是一个区间
贪心加模拟的思想
1.代码解释
#include <bits/stdc++.h>
using namespace std;
bool v[100005];
struct Sa{
int a,b,c;
}s[100005];
bool cmp(Sa x,Sa y){
return x.b < y.b;
}
我定义了一个叫做s的 结构体,s[i].a = b,s[i].b = e,s[i].c = t(这些值和题目中的数);
这一段代码靠下的cmp是排序函数
v数组就是大街,就是那个有点乱的图
___________________________优雅的分界线_______________________
输入+排序
int main(){
int q;
int n;
cin >> q>> n;
for (int i =1; i <= n; i++){
cin >> s[i].a >> s[i].b >> s[i].c;
}
sort(s+1,s+n+1,cmp);
___________________________优雅的分界线_______________________
int cnt = 0;
for (int i = 1; i <= n; i++){
int sum = 0;
for (int j = s[i].a; j <= s[i].b; j++){
if (v[j]) sum ++;
}
if (sum >= s[i].c){
continue;
}
循环的第一部分, sum的值是在这个区间内种了多少棵树,如果已经种够了那就不用循环这一次看下一次区间.
___________________________优雅的分界线_______________________
sum = s[i].c - sum;
cnt += sum;
for (int j = s[i].b; j >= s[i].a; j--){
if (v[j] == 0){
sum --;
v[j] = 1;
}
if (sum == 0){
break;
}
}
循环的第二部分,sum值变成还差多少棵树,总共棵数增加sum,循环种树(倒过来循环);
___________________________优雅的分界线_______________________
最后输出cnt;
___________________________优雅的分界线_______________________
总体代码
#include <bits/stdc++.h>
using namespace std;
bool v[100005];
struct Sa{
int a,b,c;
}s[100005];
bool cmp(Sa x,Sa y){
return x.b < y.b;
}
int main(){
int q;
int n;
cin >> q>> n;
for (int i =1; i <= n; i++){
cin >> s[i].a >> s[i].b >> s[i].c;
}
sort(s+1,s+n+1,cmp);
int cnt = 0;
for (int i = 1; i <= n; i++){
int sum = 0;
for (int j = s[i].a; j <= s[i].b; j++){
if (v[j]) sum ++;
}
if (sum >= s[i].c){
continue;
}
sum = s[i].c - sum;
cnt += sum;
for (int j = s[i].b; j >= s[i].a; j--){
if (v[j] == 0){
sum --;
v[j] = 1;
}
if (sum == 0){
break;
}
}
}
cout << cnt;
return 0;
}
3.方法二
换1种方法可以反过来运算,就像加有减,除有乘
也就是从左到右去种树,排序的时候按照左端点从小到大排序,
直接演示代码吧!
#include <bits/stdc++.h>
using namespace std;
bool v[100005];
struct Sa{
int a,b,c;
}s[100005];
bool cmp(Sa x,Sa y){
return x.a > y.a;
}
int main(){
int q;
int n;
cin >> q>> n;
for (int i =1; i <= n; i++){
cin >> s[i].a >> s[i].b >> s[i].c;
}
sort(s+1,s+n+1,cmp);
int cnt = 0;
for (int i = 1; i <= n; i++){
int sum = 0;
for (int j = s[i].a; j <= s[i].b; j++){
if (v[j]) sum ++;
}
if (sum >= s[i].c){
continue;
}
sum = s[i].c - sum;
cnt += sum;
for (int j = s[i].a; j <= s[i].b; j++){
if (v[j] == 0){
sum --;
v[j] = 1;
}
if (sum == 0){
break;
}
}
}
cout << cnt;
return 0;
}
3.区间合并问题
P2434 [SDOI2005] 区间
网址:
P2434 [SDOI2005] 区间 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P2434
1. 思路分析
给出若干个区间让我们输出它们可以合并出来最少的区间(个数,但是输出区间)。
1.按照左端点排序
2.排序完了以后定义两个变量L和R,L=第一个区间的左端点,R=第一个区间的右端点
3.遍历一遍这些区间(不包含第一个)
4.如果这个区间的左端点(开端),大于了R(这个区间不在我们L~R里不包含它),输出L和R,把L的值更新为这个区间的左端点(更新一下整个区间)
5. R的值和区间的右端点做比较选取最大的那个值更新R
2.上代码
#include <bits/stdc++.h>
using namespace std;
struct B{
int l,r;
}a[1000005];
bool cmp(B x, B y){
return x.l < y.l;
}
int main(){
int n;
cin >> n;
for (int i = 0; i < n; i++){
cin >> a[i].l >> a[i].r;
}
sort(a,a+n,cmp);
int R = a[0].r,L = a[0].l;
for (int i=1; i <n ; i++){
if (a[i].l > R){
cout << L << " " << R << "\n";
L = a[i].l;
}
R = max(a[i].r,R);
}
cout << L << " " << R << "\n";
return 0;
}
4.区间覆盖问题
P1668 [USACO04DEC] Cleaning Shifts S
P1668 [USACO04DEC] Cleaning Shifts S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1668
这道题题目的意思是:给定我们一个区间1~T,我们有N个小区间,请问需要最少几个小区间可以覆盖整个大区间(1~T),请注意两个区间之间可以差一,只要每个时间段都有奶牛也可以,比如(1,2)(3,4)它们就可以并到一起去。虽然它讲起来是时间段但是它做起来是时间点。
1.思路分析
图片分析请看下图
2.代码解释
#include <bits/stdc++.h>
using namespace std;
struct Sa{
int a,b;
}s[100005];
bool cmp(Sa x,Sa y){
return x.a < y.a;
}
int main(){
int n,t;
cin >> n >> t;
int cnt=0;
for (int i= 1; i <= n; i++){
cin >> s[i].a >> s[i].b;
}
sort(s+1,s+n+1,cmp);
结构体函数+排序函数cmp按照左端点从小到大排+输入与排序
int l = 1,r = -1;
for (int i = 1; i <= n;){
if (s[i].a > l){
cout <<-1;
return 0;
}
如果左端点最靠前的那个区间,左端点还是大于L,说明整个区间结构体没有一个左端点小于等于L,输出负一不能实现
while (i <= n and s[i].a <= l){
r = max(s[i].b,r);
i++;
}
cnt ++;
去看,左端点小于等于,而且右端点是最大的.增加区间段数(cnt).
if (r >= t){
cout << cnt;
return 0;
}
l = r +1;
}
如果我们的最大值已经超过了T输出cnt,把L更新为r+1.
cout << -1;
return 0;
}
对如果在循环里都没结束的话,说明不可以实现输出-1
总体代码
#include <bits/stdc++.h>
using namespace std;
struct Sa{
int a,b;
}s[100005];
bool cmp(Sa x,Sa y){
return x.a < y.a;
}
int main(){
int n,t;
cin >> n >> t;
int cnt=0;
for (int i= 1; i <= n; i++){
cin >> s[i].a >> s[i].b;
}
sort(s+1,s+n+1,cmp);
int l = 1,r = -1;
for (int i = 1; i <= n;){
if (s[i].a > l){
cout <<-1;
return 0;
}
while (i <= n and s[i].a <= l){
r = max(s[i].b,r);
i++;
}
cnt ++;
if (r >= t){
cout << cnt;
return 0;
}
l = r +1;
}
cout << -1;
return 0;
}
5.区间分组
呃……,这道题你们就看图片吧!
T471772 cici排课
1.一点点思路
这个题目不在于老师的编号是多少哪个老师该上哪节课,只用求出老师的个数就行。
只要有课程下课了的话我们就可以释放老师,如果释放了新课,既要把新课排给被释放的老师.
2.代码实现与算法思路
#思路
我们输给两个数组(a,b),注意在这里不要用区间的眼光看这两个数组, 数组要分开来排序, a从小到大排序b也从小到大排序,定义一个变量cnt(老师数量)然后循环:
a的i号位的数与b的第j号位对比,如果小于等于B的第j号位{
cnt++
查看a的i+1号位//也就是i++;
}//这个循环也就是循环到a[i]>b[j],看一看在b[j]这个课结束之前,还会上多少节课
如果b[j]比a[i]为小的话{//也就是说有老师被释放出来了
cnt --
j++;//看下一位
}
#代码实现,增加一些判断我会进行注释
为什么要求最大值,因为这种方法是最值的,而求最大值是要求这种最值方法需要多少名老师
#include <bits/stdc++.h>
using namespace std;
int a[100005],b[100005];
int main(){
int n;
cin >> n;
for (int i =0; i < n; i++){
cin >> a[i] >> b[i];
}
sort(a,a+n);
sort(b,b+n);
int i = 0,j = 0,big = -1,cnt = 0;
while (1){
while (i < n and a[i] <= b[j]){
cnt ++;
big = max(cnt,big);//求最大值
i ++;
}
if (i >= n) break; //如果要上的课都检查完了就退出输出最大值
while (j < n and b[j] < a[i]){
cnt --;
j ++;
}
}
cout << big;
return 0;
}
3.举一反三
这道题会让我们想起“匹配括号”,虽然不知道这个题名字叫做什么,但是题目的大致意思如下:
输入一个字符串字符串用“(”和“)”组成,“(”和“)”可以形成一组,请问输入的字符串有没有多余的括号。如果没有输出“yes”如果有输出“no”(不加引号)。
就比如输入:
((())())
很明显是匹配上了的
请看下列表格是我们这道题的算法,左括号加一,右括号减一
( | ( | ( | ) | ) | ( | ) | ) |
1 | 2 | 3 | 2 | 1 | 2 | 1 | 0 |
如果最后等于零的话就可以匹配成功.
不过你可能会问这道题和cici排课有什么关系 ,你可以把题目改编一下变成:
还是输入一个字符串,每一个符号(左括号、右括号)之间,输出我们的cnt最大值。
我们借鉴上面的表格,这个题目就可以输出3
而我们的CiCi排课也是同样的道理,左括号是开始上课,右括号是结束上课,问你老师人数的最大值.
3.遇到了(区间)贪心的题我们应该怎么做
找出我们要排序的顺序按什么排序,
再进行循环,加入题目的要求
最后说一句特殊题目特殊判断,一定要变得灵活起来.