算法基础课-贪心

贪心

贪心能解决的问题是动态规划能解决的问题的子集

贪心的正确性证明在校内应该是作为一个重难点讲解的

贪心的试做:先按照某个特征进行排序,然后按照顺序进行操作


有关区间的贪心

区间选点

请添加图片描述

贪心思路

  • 首先对所有区间按照右端点从小到大排序
  • 按顺序遍历所有区间:
    • 如果该区间内包含至少一个点了
      • pass
    • 如果该区间还没有包含任何点
      • 将该区间的右端点加入点集

正确性证明

  • 首先设按照贪心思路得到的答案是 c n t cnt cnt,真正的答案是 a n s ans ans
  • 首先证明 a n s ≤ c n t ans \leq cnt anscnt
    • 按照贪心思路进行选择,可以知道选择的点至少能满足“每个区间都包含点”
    • 由于 a n s ans ans是所有选点的数量的下界,所以有 a n s ≤ c n t ans \leq cnt anscnt
  • 证明 c n t ≤ a n s cnt \leq ans cntans
    • 考察所有没被pass掉的区间
      • 由于每次都是从小到大选择右端点
      • 所以每次没被pass的区间与之前所有区间都没有交集
    • 于是 c n t cnt cnt至多被所有区间的一个子集所包含:即那些没有交集的区间所组成的子集
    • 由于 a n s ans ans的要求是让所有区间包含,由于有限子集的势至少小于等于全集
    • 所以 c n t ≤ a n s cnt \leq ans cntans

代码思路:

  • 由于点集是递增的
  • 所以当且仅当,当前区间的左端点比点集里最右端的点还右,加入新点
  • 所以只需要记录一下点集里最右边的端点即可
#include <iostream>
#include <algorithm>
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() {
	scanf("%d", &n);
	for (int i = 0; i < n; i ++ ) scanf("%d%d", &range[i].l, &range[i].r);

	sort(range, range + n);

	int res = 0, ed = -2e9;//ed是当前最右端点
	for (int i = 0; i < n; i ++ )
		if (ed < range[i].l) {
			res ++ ;
			ed = range[i].r;
		}

	printf("%d\n", res);

	return 0;
}

最大不相交区间数量

请添加图片描述 本题和上一题是同一答案


区间分组

请添加图片描述

贪心思路:

  • 将所有区间按照左端点从小到大排序
  • 从前往后遍历每个区间
    • 遍历当前已有的所有组
      • 如果当前区间能放进某个组,放
    • 如果遍历完发现不存在这样的组,开辟一个新租

正确性证明

  • 首先设按照贪心思路得到的答案是 c n t cnt cnt,真正的答案是 a n s ans ans
  • 首先证明 a n s ≤ c n t ans \leq cnt anscnt
    • 按照贪心思路进行选择,可知至少是个合法方案
    • 由于 a n s ans ans是所有选点的数量的下界,所以有 a n s ≤ c n t ans \leq cnt anscnt
  • 证明 c n t ≤ a n s cnt \leq ans cntans
    • c n t cnt cnt组中,一定能找到至少 c n t cnt cnt个区间,使得其两两相交都不为空
    • 所以这 c n t cnt cnt个区间至少要被分配到 c n t cnt cnt个组里面去

代码思路

  • 判断某个区间能不能放进某个组:
    • 判断当前区间的左端点是不是比这个组的最右端还左
    • 所以每个组只需要储存一个最右端端点值作为代表点即可
  • 快速判断要不要开新组:
    • 用小根堆维护所有组最右端点的最小值
#include <bits/stdc++.h>
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;
		scanf("%d%d", &l, &r);
		range[i] = {l, r};
	}

	sort(range, range + n);

	priority_queue<int, vector<int>, greater<int>> heap;
	for (int i = 0; i < n; i ++ ) {
		auto r = range[i];
		if (heap.empty() || heap.top() >= r.l) heap.push(r.r);//开新组
		else {//把当前区间放到最小值所在的组
			heap.pop();
			heap.push(r.r);
		}
	}

	printf("%d\n", heap.size());

	return 0;
}

区间覆盖

请添加图片描述
贪心思路:

  • 将所有区间按照左端点从小到大排序
  • 从前往后遍历每个区间
    • 在所有能够覆盖 s s s区间之中,选择右端点最大的区间,加入覆盖集
    • s s s更新为该右端点
#include <bits/stdc++.h>
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;
		scanf("%d%d", &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) { //左端点比st小
			r = max(r, range[j].r);
			j ++ ;
		}

		if (r < st) {//最大值都比st小,不存在解
			res = -1;
			break;
		}

		res ++ ;//记录
		if (r >= ed) {
			success = true;
			break;
		}

		st = r;//更新
		i = j - 1;//更新快指针
	}

	if (!success) res = -1;
	printf("%d\n", res);

	return 0;
}

Huffman树


请添加图片描述

贪心策略:

每次选择最小的两堆果子合并,然后把新堆加入

#include <bits/stdc++.h>
using namespace std;

int main() {
	int n;
	scanf("%d", &n);

	priority_queue<int, vector<int>, greater<int>> heap;
	while (n -- ) {
		int x;
		scanf("%d", &x);
		heap.push(x);
	}

	int res = 0;
	while (heap.size() > 1) {
		int a = heap.top();
		heap.pop();
		int b = heap.top();
		heap.pop();
		res += a + b;
		heap.push(a + b);
	}

	printf("%d\n", res);
	return 0;
}

杂题

打水问题

请添加图片描述

小学奥数,本质上是排序不等式

两 组 有 序 序 列 { x i } , { y i } 两组有序序列\{x_i\},\{y_i\} {xi},{yi}

x 1 ≤ x 2 ≤ ⋯ ≤ x n y 1 ≤ y 2 ≤ ⋯ ≤ y n x_1 \leq x_2 \leq \dots \leq x_n \\ y_1 \leq y_2 \leq \dots \leq y_n \\ x1x2xny1y2yn

则 则

x n y 1 + x n − 1 y 2 + ⋯ + x 1 y n ≤ x σ ( 1 ) y 1 + x σ ( 2 ) y 2 + ⋯ + x σ ( n ) y n ≤ x 1 y 1 + x 2 y 2 + ⋯ + x n y n x_ny_1 + x_{n-1}y_2+ \dots +x_1y_n \leq x_{\sigma (1)}y_1 + x_{\sigma (2)}y_2 + \dots + x_{\sigma (n)}y_n \leq x_1y_1 +x_2y_2 + \dots + x_ny_n xny1+xn1y2++x1ynxσ(1)y1+xσ(2)y2++xσ(n)ynx1y1+x2y2++xnyn

即   倒 序 ≤ 乱 序 ≤ 顺 序 即 \ 倒序 \leq 乱序 \leq 顺序  

证明方法是调整法

所以本题就是每次都让时间最短的先打水


货舱地址

请添加图片描述
经典选择中位数:绝对值和的最小值
(本题有线性时间做法,见线性时间选择,是一个分治算法)


耍杂技的牛

请添加图片描述
考虑相邻两头牛的交换:
请添加图片描述
所以如果存在 W i + S i > W i + 1 + S i + 1 W_i + S_i > W_{i+1}+S_{i+1} Wi+Si>Wi+1+Si+1
则交换这相邻的两项,其危险度的最大值,至少不会变大

于是对于任意一个最优解,我们总能不断地对其进行交换,直到不能交换为止,这样交换得到的解也一定还是最优解

这就证明了按照 W i + S i W_i+S_i Wi+Si从上到下降序排序的一个序列总是最优解
于是就成为了一个贪心解法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值