贪心
贪心能解决的问题是动态规划能解决的问题的子集
贪心的正确性证明在校内应该是作为一个重难点讲解的
贪心的试做:先按照某个特征进行排序,然后按照顺序进行操作
有关区间的贪心
区间选点
贪心思路
- 首先对所有区间按照右端点从小到大排序
- 按顺序遍历所有区间:
- 如果该区间内包含至少一个点了
- pass
- 如果该区间还没有包含任何点
- 将该区间的右端点加入点集
- 如果该区间内包含至少一个点了
正确性证明
- 首先设按照贪心思路得到的答案是 c n t cnt cnt,真正的答案是 a n s ans ans
- 首先证明
a
n
s
≤
c
n
t
ans \leq cnt
ans≤cnt
- 按照贪心思路进行选择,可以知道选择的点至少能满足“每个区间都包含点”
- 由于 a n s ans ans是所有选点的数量的下界,所以有 a n s ≤ c n t ans \leq cnt ans≤cnt
- 证明
c
n
t
≤
a
n
s
cnt \leq ans
cnt≤ans
- 考察所有没被pass掉的区间
- 由于每次都是从小到大选择右端点
- 所以每次没被pass的区间与之前所有区间都没有交集
- 于是 c n t cnt cnt至多被所有区间的一个子集所包含:即那些没有交集的区间所组成的子集
- 由于 a n s ans ans的要求是让所有区间包含,由于有限子集的势至少小于等于全集
- 所以 c n t ≤ a n s cnt \leq ans cnt≤ans
- 考察所有没被pass掉的区间
代码思路:
- 由于点集是递增的
- 所以当且仅当,当前区间的左端点比点集里最右端的点还右,加入新点
- 所以只需要记录一下点集里最右边的端点即可
#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
ans≤cnt
- 按照贪心思路进行选择,可知至少是个合法方案
- 由于 a n s ans ans是所有选点的数量的下界,所以有 a n s ≤ c n t ans \leq cnt ans≤cnt
- 证明
c
n
t
≤
a
n
s
cnt \leq ans
cnt≤ans
- 在 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 \\ x1≤x2≤⋯≤xny1≤y2≤⋯≤yn
则 则 则
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+xn−1y2+⋯+x1yn≤xσ(1)y1+xσ(2)y2+⋯+xσ(n)yn≤x1y1+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从上到下降序排序的一个序列总是最优解
于是就成为了一个贪心解法