区间贪心

目录

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)icon-default.png?t=N7T8https://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)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1250

1.题目

 

2.方法一

 把所有树都往右边种,为了让他们重叠区间的更多,而且重叠的部分大部分都在右侧,我们就要让这些区间按右端点,从小到大排序,下图是我列举的样例。

首先先把样例归一整一下(排序)

123456789
||
|
|

从第一个开始在尾巴上种树,如果这个区间内已经种到了t[i]棵的话就不种了 , 如果没有种到这么多棵树那就得从后往前,循环种树只要种到为止.(括号代表已经种上树了,+1代表多种一棵树)

123456789
|(|)+1(】)+1
(【)(|)(】)被底下的人种上了
(【)(|)+1
(【)+1(】)+1

其实这样的话更形象,就是有点乱:

123456789
([)|(|[)(]|[)(]|)]([)(])

 同样颜色是一个区间

贪心加模拟的思想

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)icon-default.png?t=N7T8https://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)icon-default.png?t=N7T8https://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”(不加引号)。

就比如输入:

((())())

很明显是匹配上了的

请看下列表格是我们这道题的算法,左括号加一,右括号减一

12321210

如果最后等于零的话就可以匹配成功.

不过你可能会问这道题和cici排课有什么关系 ,你可以把题目改编一下变成:

还是输入一个字符串,每一个符号(左括号、右括号)之间,输出我们的cnt最大值。

我们借鉴上面的表格,这个题目就可以输出3

而我们的CiCi排课也是同样的道理,左括号是开始上课,右括号是结束上课,问你老师人数的最大值.

3.遇到了(区间)贪心的题我们应该怎么做

找出我们要排序的顺序按什么排序,

再进行循环,加入题目的要求

最后说一句特殊题目特殊判断,一定要变得灵活起来.

end👍🏻⭐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值