贪心算法
区间问题
区间覆盖问题
题目描述
给定 $N$ 个闭区间 $[a_i, b_i]$,请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
注意:位于区间端点上的点也算作区间内。
输入格式
- 第一行包含一个整数 $N$,表示区间的数量。
- 接下来 $N$ 行,每行包含两个整数 $a_i$ 和 $b_i$,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需的点的最小数量。
数据范围
- $1 \leq N \leq 10^5$
- $-10^9 \leq a_i \leq b_i \leq 10^9$
输入样例
输出样例
解题思路
区间按右端点非递减排序,每次选取右端点。
证明
假设最优解是 $res$ ,贪心算法的解是 $cnt$ ,那么有: $res \leq cnt$ ;按照贪心算法,只有区间不相交时才会选取,意即至少有 $cnt$ 个不相交的区间,至少需要 $cnt$ 个点,那么有 $res \ge cnt$ ;因此 $res = cnt$ 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <iostream> #include <cstring> #include <algorithm> #include <vector>
using namespace std;
const int N = 1e5 + 10;
int n;
bool cmp(pair<int,int> a, pair<int,int> b) { return a.second < b.second; }
int main() { cin >> n; int a, b; vector<pair<int,int>> segs; for(int i = 0; i < n; ++ i) { cin >> a >> b; segs.push_back(make_pair(a, b)); } sort(segs.begin(), segs.end(), cmp); int ed = -2e9, res = 0; for(auto seg: segs) if(ed < seg.first) ++ res, ed = seg.second; cout << res << endl; return 0; }
|
区间调度问题
题目描述
给定 $N$ 个闭区间 $[a_i, b_i]$,请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
输入格式
- 第一行包含一个整数 $N$,表示区间数。
- 接下来 $N$ 行,每行包含两个整数 $a_i, b_i$,表示一个区间的两个端点。
输出格式
输出一个整数,表示可选取的区间的最大数量。
数据范围
- $1 \leq N \leq 10^5$
- $-10^9 \leq a_i \leq b_i \leq 10^9$
输入样例
输出样例
解题思路
以右端点非递减排序,遍历,每次比较当前区间的左端点与 $ed$ ,若大于,则选择。
证明
假设最优解与贪心解分别为 $res, cnt$ , 那么有 $res \ge cnt$;考虑 $cnt$ 的求法,每一个选中的区间必然 $pass$ 掉若干右端点比它大的区间,并且这些区间的左端点小于该区间的右端点,将这些区间看成一个集合,因此共有 $cnt$ 个集合,每一个集合内的区间都两两相交;根据抽屉原理,若 $ans \gt cnt$ ,则 $ans$ 中必然有两个区间在同一个集合,矛盾,因此 $ans \le cnt$, 因此 $ans = cnt$。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream> #include <cstring> #include <algorithm> #include <vector>
using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int> PII;
#define x first #define y second
bool cmp(PII a, PII b) { return a.y < b.y; }
int n, a, b; vector<PII> segs;
int main() { cin >> n; for(int i = 0; i < n; ++ i) {cin >> a >> b; segs.push_back(make_pair(a, b));} sort(segs.begin(), segs.end(), cmp); int st, ed; st = ed = -2e9; int res = 0; for(auto seg: segs) { if(ed < seg.x) ++ res, st = seg.x, ed = seg.y; } cout << res << endl; return 0; }
|
区间分组问题
题目描述
给定 $N$ 个闭区间 $[a_i, b_i]$,请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
输入格式
- 第一行包含整数 $N$,表示区间数量。
- 接下来 $N$ 行,每行包含两个整数 $a_i, b_i$,表示一个区间的左右端点。
输出格式
输出一个整数,表示将所有区间分组后的最小组数。
数据范围
- $1 \le N \le 10^5$
- $-10^9 \le a_i \le b_i \le 10^9$
输入样例
输出样例
解题思路
将区间按照左端点排序,遍历,每次判断该区间是否能放入某个集合,若不能,则新开一个集合;对于每个集合,维护一个最大右端点值,由于是以左端点排序,那么只要当前遍历到的区间的左端点大于右端点,就能放入到该集合内;进一步的,维护所集合的最右端点值,每次尽可能放入具有最小右端点值的集合。
证明思路
按照算法,每次引起新建集合的区间必定和当前集合的所有区间两两相交,因此至少需要 $cnt$ 个集合,有 $res \ge cnt$;
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <iostream> #include <cstring> #include <algorithm> #include <vector> #include <queue>
using namespace std;
const int N = 1e5 + 10;
struct e{ int l, r; bool operator < (const e &w)const{ return l < w.l; } }segs[N];
int n;
int main() { cin >> n; for(int i = 1; i <= n; ++ i) cin >> segs[i].l >> segs[i].r; sort(segs + 1, segs + n + 1); priority_queue<int, vector<int>, greater<int>> heap; for(int i = 1; i <= n; ++ i) { if(heap.empty() || heap.top() >= segs[i].l) heap.push(segs[i].r); else heap.pop(), heap.push(segs[i].r); } cout << heap.size() << endl; return 0; }
|
区间覆盖问题(指定区间完全覆盖)
题目描述
给定 $N$ 个区间 $[a_i, b_i]$ 以及一个目标区间 $[s, t]$,请你从这 $N$ 个区间中选择尽量少的区间,使得所选区间的并集能够完全覆盖目标区间 $[s, t]$。如果无法覆盖则输出 $-1$。
输入格式
- 第一行包含两个整数 $s$ 和 $t$,表示目标区间的左右端点。
- 第二行包含一个整数 $N$,表示给定区间的数量。
- 接下来 $N$ 行,每行包含两个整数 $a_i, b_i$,表示第 $i$ 个区间的左右端点。
输出格式
输出一个整数,表示所需的最少区间数量;如果无法完全覆盖则输出 -1。
数据范围
- $1 \le N \le 10^5$
- $-10^9 \le a_i \le b_i \le 10^9$
- $-10^9 \le s \le t \le 10^9$
输入样例
输出样例
解题思路
以左端点排序,每次选择尽可能覆盖更多的区间。
证明
按照算法,每次选择的都是左端点小于 $st$ ,右端点尽可能大的区间;如果 $ans \lt cnt$ , 那么 $cnt$ 中必然至少有两个区间可以被一个区间代替,这与算法矛盾,因为这样的区间必然会被算法在前次遍历时选取,因此 $an \ge cnt$
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <cstring> #include <algorithm> #include <vector>
using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int> PII;
PII segs[N];
#define x first #define y second
bool cmp(PII a, PII b) { return a.x < b.x; }
int main() { int st, ed, n; cin >> st >> ed >> n; for(int i = 1; i <= n; ++ i) { cin >> segs[i].x >> segs[i].y; } sort(segs + 1, segs + n + 1); int res = 0; bool success = false;; for(int i = 1; i <= n; ++ i) { int j = i, b = -2e9; while(j <= n && segs[j].x <= st) { b = max(b, segs[j].y); ++ j; } if(b < st) {success = false; break;} ++ res; if(b >= ed) {success = true; break;} st = b, i = j - 1; } if(!success) cout << -1 << endl; else cout << res << endl; return 0; }
|
叠罗汉问题(奶牛垂直堆叠风险最小化)
题目描述
农民约翰的 (N) 头奶牛(编号为 (1\ldots N))计划逃跑并加入马戏团,为此它们练习“叠罗汉”杂技:奶牛站在彼此的身上,形成一个高高的垂直堆叠。每头奶牛都有重量 (W_i) 和强壮程度 (S_i)。当奶牛 (i) 站在一系列奶牛上时,其风险值定义为其上方所有奶牛的总重量(不包括自己)减去它的强壮程度:
风险值越大,这头奶牛撑不住的可能性越高。请为这些奶牛找出一种排列顺序,使得所有奶牛的风险值中的最大值尽可能小。
输入格式
- 第一行包含一个整数 (N),表示奶牛数量。
- 接下来 (N) 行,每行包含两个整数 (W_i) 和 (S_i),分别表示第 (i) 头奶牛的重量和强壮程度。
输出格式
输出一个整数,表示在最优排列下,所有奶牛的最大风险值的最小可能值。
数据范围
- $1 \le N \le 50000$
- $1 \le W_i \le 10000$
- $1 \le S_i \le 10^9$
输入样例
输出样例
解题思路
按照 $w + s$ 排序即可。
证明思路
考察相邻的两头牛 $i, i + 1$ 是否需要交换,它们的交换不影响其余牛的危险值。
交换前:$i: \sum^{i-1}{j = 1}w_j - s_i, i + 1: \sum^{i}{j=1}wj - s{i+1}$
交换后:$i: \sum^{i-1}{j=1}w_j + w{i+1} - si, i + 1: \sum^{i-1}{j=1}wj - s{i+1}$
比较交换后的 $i$ 与交换前的 $i + 1$即可。
此时假设:$\sum^{i-1}{j=1}w_j + w{i+1} - si \ge \sum^{i}{j=1}wj - s{i+1}$
即:$w{i+1} - s_i \ge w_i - s{i+1}$
即:$w{i+1} + s{i+1} \ge w_i + s_i$
即如果满足上式,则交换后的危险值会变大,意即 $w + s$ 大的应该排在后。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <iostream> #include <cstring> #include <algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
struct e{ int w, s; bool operator < (const e &x)const{ return w + s < x.w + x.s; } }cows[N];
int n;
int main() { cin >> n; for(int i = 1; i <= n; ++ i) cin >> cows[i].w >> cows[i].s; sort(cows + 1, cows + n + 1); int res = -2e9, weight = 0; for(int i = 1; i <= n; ++ i) { res = max(res, weight - cows[i].s); weight += cows[i].w; } cout << res << endl; return 0; }
|