2021-01-28 贪心

一.简单贪心

1.贪心:反证和归纳

2,分类:

(1)先处理:先排序再遍历模拟出最优
(2)边处理边选择:

「我们每次都取 XXX 中最大/小的东西,并更新 XXX。」(有时「XXX 中最大/小的东西」可以优化,比如用优先队列维护)

3.eg

eg1.国王游戏

排序处理找出最优, 要用到高精度乘除

bool cmp(dachen x,dachen y){
	return x.a*x.b<y.a*y.b;
}
eg2,工作调度

边处理边选择

#include<iostream>
#include<algorithm>
#include <math.h>

# define PI acos(-1.0)
#define MaxNum 20



#include<queue>
#include<stdio.h>
#include<stdlib.h>
#include<cstring>
#include <stdio.h>
#include <memory.h>
#define N 200001
using namespace std;
typedef long long ll;
ll n;
typedef struct nochang{
	ll d,p;
}nochang;
nochang nc[N];
bool cmp(nochang x,nochang y){
	return x.d<y.d;
}

int main(){
	ll i,j;
	
	while(cin>>n){
		priority_queue<ll,vector<ll>,greater<ll> > heap;
		for(i=0;i<n;i++){
			cin>>nc[i].d>>nc[i].p;
		}
		sort(nc,nc+n,cmp);
		ll sum=0;
		for(i=0;i<n;i++){
			if(nc[i].d<=heap.size()){
				if(nc[i].p>heap.top()){
					sum=sum-heap.top()+nc[i].p;
					heap.pop();
					heap.push(nc[i].p);
				}
			}else{
				heap.push(nc[i].p);
				sum+=nc[i].p;
			}
		}
		cout<<sum<<endl;
	}
}
eg3.分发糖果

eg4.小船过河
【Description】
一群人划船过河,河边只有一条船,这条船可以容纳两个人,船过河后需要一人将船开回,以便所有人都可以过河,每个人过河速度不一样,两个人过河速度取决于慢的那个人,请问最少需要多久让所有人过河?
【Input】
第一行输入人数n;
第二行输入每个人过河所需的时间;
【Output】
输出需要的最少时间
【Sample Input】
4
1 2 5 10
【Sample Output】
17

题目分析:两种方法,较容易想到的是第一种方式:
第一种办法:先让1 2过去(2分钟),1回来(1分钟),1 5过去(5分钟),1回来(1分钟),1 10再过去(10分钟),总共需要19分钟就可以让四个人都过去。
而正确答案是第二种办法:先让1 2过去(2分钟),1回来(1分钟),5 10过去(10分钟),2回来(2分钟),1 2再过去(2分钟),总共需要17分钟就可以让四个人都过去。
最优选择:
先将所有人过河所需的时间按照升序排序考虑把单独过河所需要时间最多的两个旅行者送到对岸去,有两种方式:
1.最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,然后次快的将船划回来,时间:t[0]+2t[1]+t[n-1];
2.最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来,
时间:2t[0]+t[n-2]+t[n-1]。
在这里插入代码片

二.区间贪心


参考

1.最大不相交区间数量

问题:给定N个闭区间[ a i , b i ] ,请你在数轴上选择若干区,使得选中的区间之间互不相交(包括端点),输出可选取区间的最大数量。
策略:选一个给后面的留更多的空间(按右端点从小到大排序),结束时间越早越靠前,选择与开始时间在上一个选择区间结束时间之后的

#define N 1000
typedef struct qujian{
	int start,end;
}qujian;
qujian q[N];
bool cmp(qujian a,qujian b){
	return a.end<b.end;
}
int n;//q的大小 
int mostQujian(){
	int i,pos;
	int g[N];
	int cnt=1;//存储不相交区间的数量 ,已包含第一个结束时间最小的区间 
	sort(q,q+n,cmp);//按结束时间从小到大排序 
	pos=q[0].end;//储存上一个区间的结束时间 
	g[1]=0;//存储最大不相交区间包括的区间下标 
	for(i=1;i<n;i++){
		if(q[i].start>pos){
			cnt++;
			pos=q[i].end;
			g[cnt]=i;
		}
		
	}
	return cnt;
}

2.区间选点

问题:n个闭区间[ai,bi],让他取尽量少的点,使得每个闭区间内至少有一个点。
策略:按右端点从小到大,第一个区间的右端点即选择的点pos,如果当前区间的左端点在上一个区间的右端,则把当前区间的右端点做pos

typedef struct qujian{
	int start,end;
}qujian;
qujian q[N];
bool cmp(qujian a,qujian b){
	return a.end<b.end;
}
int n;//q的大小 
int QujianXuandian(){
	int i,pos;
	int g[N];
	sort(q,q+n,cmp);
	pos=q[0].end;
	int cnt=1;//点的个数 
	for(i=1;i<n;i++){
		if(q[i].start>pos){
			pos=q[i].end;
			cnt++;
		}
	}
	return cnt;
}

3.区间完全覆盖

问题:给定一个长度为m的区间(全部闭合),再给出n条线段的起点和终点(注意这里是闭区间),求最少使用多少条线段可以将整个区间完全覆盖
策略:按左端点从小到大排序,右端点大的在上面,选取第一个区间的end做pos,向下选区间,当到达一个区间的左端点大于pos时,则需要添加一个区间,即再向上找到右端点>该区间左端点的,将这个区间加入,同时pos更新
,如果不存在这个区间,说明无法完全覆盖,即无解

#define N 1000
typedef struct qujian{
	int start,end;
}qujian;
qujian q[N];
bool cmp(qujian a,qujian b){
	if(a.start!=b.start)
	return a.start<b.start;
	return a.end>b.end;
}
int n;//q的大小 
int QujianFugai(){
	int i;
	int pos=0,maxpos=0;
	sort(q,q+n,cmp);
	int cnt=0; 
	
	for(i=0;i<n;i++){
		if(q[i].start<=pos){//该区间可与前面连 
			maxpos=max(maxpos,q[i].end);//连着的区间的最右端 
		}else{
			if(maxpos>=q[i].start){//能连但还没连的区间的最右端可以覆盖这个断的区间 
		        cnt++;//增加一个区间,即能连且最右的那个区间 
		        pos=maxpos;
		        --i;//i不变,进行下一个循环再更新maxpos 
			 
			 } else{//不能补,说明有断的,无解 
			 	
			 	break;
			 }
		}
		
	}
	
	 return cnt;
}

4.区间分组


参考

问题:
给定N个闭区间[ a i , b i ] ,请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。输出最小组数。
策略:
将所有区间按照左端点 递增的顺序排序。我们需要考虑的是已有的所有区间组中是否有区间与当前区间有交集,即我们可以保存所有区间组中最右区间的右端点值,若是值最小的区间与其无交点则可以存放如此组中,同时更新组的值。
但是,每次查找最小值的时间复杂度为O(n),制约了整个算法时间复杂度,故我们可以通过小根堆在O(logn)的时间内更新堆并在常数时间内获取最小值。

贪心策略: 通过堆寻找所有区间组中的最小值。

C++代码实现

#include <iostream>
#include <queue>
#include <algorithm>
#include <cstdio>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 1e5+10;
PII reg[N];

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 0; i< n; ++i) {
        scanf("%d%d", &reg[i].x, &reg[i].y);
    }
    sort(reg, reg+n);
    //小根堆
    priority_queue<int, vector<int>, greater<int> > heap;
    //堆中存放的各个集合的最右边的值
    //若某个区间左端点与堆顶冲突,则新开一个区间,否则更新堆顶元素
    for(int i = 0; i < n; ++i) {
        if(heap.empty() || heap.top() >= reg[i].x){
            //创建新的区间
            heap.push(reg[i].y);   
        }
        else{
            //更新堆顶元素
            heap.pop();
            heap.push(reg[i].y);
        }
    }
    printf("%d\n", heap.size());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值