《挑战程序设计竞赛》课后练习题解集——2.2 一往直前!贪心法

2.2 一往直前!贪心法

区间

POJ 2376 给出N个区间,要求最小数量的区间能覆盖[1,T]

假设从左往右覆盖区间,当前已覆盖至[1,t],则贪心地从剩余区间中左端点≤t+1的选取右端点最大的。因为右端点越大,覆盖的范围越大。

因此,对所有区间按左端点排序,扫一遍模拟选取过程即可

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 #define pii pair<int, int>
 6 #define fi first
 7 #define se second
 8 
 9 const int maxn = 3e4 + 5;
10 
11 int n, t, over, cnt, number;
12 pii a[maxn];
13 
14 int main() {
15     scanf("%d%d", &n, &t);
16     for (int i = 0; i < n; i++) scanf("%d%d", &a[i].fi, &a[i].se);
17     sort(a, a + n);
18     while (over < t && cnt < n && a[cnt].fi <= over + 1) {
19         int last = over;
20         while (cnt < n && a[cnt].fi <= over + 1) {
21             last = max(last, a[cnt].se);
22             cnt++;
23         }
24         over = last;
25         number++;
26     }
27     printf("%d\n", over < t ? -1 : number);
28 }
View Code

POJ 1328 给出n个岛屿坐标,要在X轴上用尽量少的灯塔覆盖到这些岛屿

用岛屿坐标找灯塔的区间,代表在这一段区间里至少要有一个灯塔。对这些区间按右端点排序。从左往右扫,灯塔尽可能往后放,这样后面的区间被满足的可能性会更大

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <iostream>
 5 using namespace std;
 6 
 7 struct inf {
 8     double a, b;
 9 } inter[1001];
10 
11 bool cmp(inf x1, inf x2) {
12     return x1.b < x2.b || (x1.b == x2.b && x1.a < x2.a);
13 }
14 
15 int n;
16 double d, x, y;
17 
18 int main() {
19     for (int T = 1; scanf("%d%lf", &n, &d) != EOF && n != 0; T++) {
20         int fail = 0;
21         for (int i = 0; i < n; i++) {
22             scanf("%lf%lf", &x, &y);
23             if (y > d || fail) {
24                 fail = 1;
25                 continue;
26             } else {
27                 double delta = sqrt(d * d - y * y);
28                 inter[i] = {x - delta, x + delta};
29             }
30         }
31         printf("Case %d: ", T);
32         if (fail) {
33             printf("-1\n");
34             getchar();
35             continue;
36         }
37         sort(inter, inter + n, cmp);
38         int res = 0;
39         for (int i = 0; i < n;) {
40             res++;
41             double ed = inter[i].b;
42             while (i < n && inter[i].a <= ed) i++;
43         }
44         printf("%d\n", res);
45         getchar();
46     }
47 }
View Code

POJ 3190 给出n个cow的进食时间区间,用最少的喂食位置(同一时刻只能有一只cow),满足所有cow的进食需求

这题数据是多组数据输入,题目未提到

 思路:对所有区间按开始时刻排序,假设当前cow_i要进食,已有喂食位置的cow结束进食时间是 t1 ≤ t2 ≤ … ≤ tx ,则贪心地选择小于 s_i 的 t_中最大的位置顶替上去,若没有,新开一个喂食位置给这个cow。后续会发现,只需要选择与 t1 比较就行(注释中的代码)

证明与t1比较即可的思路:被新cow填上去后,结束位置必然是e_i,那么现在就希望让其他进食结束早一些,这样有利于后面的cow选择,如果选择小于 s_i 的 t_中最大的位置顶替上去就是我要的效果,代码也这样写了。后来看其他人的题解,大多用优先队列只与比较t1。原因是,后面的cow s_j ≥ s_i ( j ≥ i ),我省下来的空间对后面的cow来说都一样。

为什么是左端点排序?因为这样可以让进食位置闲置的时间减少(从“我这样选了”会比“不这样选”优越在哪里考虑)

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 #include <set>
 5 #include <vector>
 6 using namespace std;
 7 #define pii pair<int, int>
 8 #define fi first
 9 #define se second
10 
11 const int maxn = 5e4 + 5;
12 const int inf = 0x7f7f7f7f;
13 
14 struct itv {
15     int s, e, id;
16     bool operator<(const itv &i) const { return s < i.s; }
17 };
18 
19 struct Point {
20     int end, id;
21     bool operator<(const Point &p) const {
22         if (end == p.end) return id > p.id;
23         return end > p.end;
24     }
25 };
26 
27 int n, kind[maxn], cnt;
28 itv p[maxn];
29 set<Point> s;
30 
31 int main() {
32     while (scanf("%d", &n) != EOF) {
33         s.clear();
34         cnt = 0;
35         for (int i = 0; i < n; i++) {
36             scanf("%d%d", &p[i].s, &p[i].e);
37             p[i].id = i;
38         }
39         sort(p, p + n);
40         for (int i = 0; i < n; i++) {
41             set<Point>::iterator ite = s.upper_bound(Point{p[i].s, -1});
42             if (ite == s.end()) {
43                 kind[p[i].id] = ++cnt;
44                 s.insert(Point{p[i].e, cnt});
45             } else {
46                 kind[p[i].id] = ite->id;
47                 s.insert(Point{p[i].e, ite->id});
48                 s.erase(ite);
49             }
50             /*
51                 if (!s.empty() && p[i].s > s.rbegin()->end) {
52                     kind[p[i].id] = s.rbegin()->id;
53                     s.erase(--s.end());
54                     s.insert(Point{p[i].e, kind[p[i].id]});
55                 } else {
56                     kind[p[i].id] = ++cnt;
57                     s.insert(Point{p[i].e, cnt});
58                 }
59             */
60         }
61         printf("%d\n", cnt);
62         for (int i = 0; i < n; i++) printf("%d\n", kind[i]);
63     }
64 }
View Code

考虑区间的贪心的思路,主要往排序上想,哪怕不知道怎么贪,也可以先假设按左端点或者右端点排序,再假设怎么选取,其实可能的方案也不多,能解释样例和一些特殊情况之后就可以考虑怎么严格证明。证明的思路主要是,“我这样选了”会比“不这样选”优越在哪里。

其他

POJ 2393  给出N周生产奶酪的单价和需要卖出的量,另外奶酪可以储存起来留着下周卖,每周每单位奶酪储存费为S,要最小化总费用

维护一个top变量代表当前周的最低价格

 1 #include <cstdio>
 2 using namespace std;
 3 
 4 #define ll long long
 5 
 6 int n, s, top = 5000, x, y;
 7 ll res;
 8 
 9 int min(int a, int b) { return a < b ? a : b; }
10 
11 int main() {
12     scanf("%d %d", &n, &s);
13     for (int i = 0; i < n; i++) {
14         scanf("%d %d", &x, &y);
15         top = min(top + s, x);
16         res += top * y;
17     }
18     printf("%lld\n", res);
19 }
View Code

POJ 1017  给出1×1到6×6种尺寸的包裹的个数,用尽量少的6×6箱子装起来

统计装3×3~5×5的包裹的箱子的空位,分成2×2的数量和1×1的数量,并尽可能转化为前者。然后就可以计算2×2和1×1是否还需要空间

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int a[10], tot, res;
 7 
 8 int max(int a, int b) { return a < b ? b : a; }
 9 
10 int main() {
11     while (true) {
12         tot = 0;
13         for (int i = 1; i <= 6; i++) {
14             scanf("%d", &a[i]);
15             tot += a[i];
16         }
17         if (tot == 0) break;
18         res = (a[3] + 3) / 4 + a[4] + a[5] + a[6];
19         int r1 = a[5] * 11, r2 = a[4] * 5;
20 
21         a[3] %= 4;
22         if (a[3] == 1)
23             r1 += 7, r2 += 5;
24         else if (a[3] == 2)
25             r1 += 6, r2 += 3;
26         else if (a[3] == 3)
27             r1 += 5, r2 += 1;
28 
29         if (a[2] >= r2) {
30             a[2] -= r2;
31             res += (a[2] + 8) / 9, a[2] %= 9;
32             if (a[2] > 0) r1 += 36 - 4 * a[2];
33         } else {
34             r1 += (r2 - a[2]) * 4;
35         } 
36 
37         a[1] = max(a[1] - r1, 0);
38         res += (a[1] + 35) / 36;
39 
40         printf("%d\n", res);
41     }
42 }
View Code

POJ 3040 给出N种面值的硬币及其数量(≤1e9),并且这些面值是互相整除。每周需要支付至少C的津贴,问最多能支付几周

有一定难度的好题。当要从剩下的钱中凑一个C出来时,总是贪心地优先选大的面额。(因为不选这个面额改用小面额时,这些小面额加起来一部分还是等于这个面额,但“灵活性”变差了)并且计算出能否以此取法重复多次,不然会T。每一次凑面额的时候至少有一种面额会“用尽”,复杂度O(N^2) (复杂度还需考虑……)

 1 #include <assert.h>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <iostream>
 5 #include <vector>
 6 using namespace std;
 7 #define pii pair<int, int>
 8 #define fi first
 9 #define se second
10 
11 int n, c, res, s;
12 pii p[25];
13 
14 int max(int a, int b) { return a > b ? a : b; }
15 int min(int a, int b) { return a < b ? a : b; }
16 
17 int main() {
18     scanf("%d %d", &n, &c);
19     for (int i = 0; i < n; i++) scanf("%d %d", &p[i].fi, &p[i].se);
20     sort(p, p + n, greater<pii>());
21     while (true) {
22         int tmp = c;
23         vector<int> use(n);
24         for (int i = 0; i < n; i++) {
25             int q = min(tmp / p[i].fi, p[i].se);
26             use[i] = q;
27             tmp -= q * p[i].fi;
28         }
29         if (tmp) {
30             for (int i = n - 1; i >= 0; i--) {
31                 if (p[i].fi >= tmp && use[i] + 1 <= p[i].se) {
32                     tmp -= p[i].fi;
33                     use[i]++;
34                     break;
35                 }
36             }
37         }
38         if (tmp > 0) break;
39         int con = (int)1e8 + 5;
40         for (int i = 0; i < n; i++) {
41             if (use[i]) con = min(con, p[i].se / use[i]);
42         }
43         res += con;
44         for (int i = 0; i < n; i++) {
45             p[i].se -= use[i] * con;
46         }
47     }
48     printf("%d\n", res);
49 }
View Code

POJ 1862 有n个stripies,权值wi,两两结合成2×sqrt(wi×wj) ,问最小的可能结果

考虑最后答案式,会发现结合次数越多套的根号越多,所以让权值大的先结合,把权值降下来。又由于两个结合后权值大于原来第二大的权值,所以加入新集合后一定是最大的,可以直接让它和剩下的第二大结合。

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <iostream>
 5 using namespace std;
 6 
 7 int n;
 8 double a[105], res;
 9 
10 int main() {
11     scanf("%d", &n);
12     for (int i = 0; i < n; i++) scanf("%lf", &a[i]);
13     if (n == 1)
14         printf("%.3f\n", a[0]);
15     else {
16         sort(a, a + n, greater<double>());
17         res = 2 * sqrt(a[0] * a[1]);
18         for (int i = 2; i < n; i++) res = 2 * sqrt(res * a[i]);
19         printf("%.3f\n", res);
20     }
21 }
View Code

POJ 3262 N头cow,每秒吃Di花,农夫需要2Ti时间把一只cow运回窝。求最少损失多少花

考虑对于一种运送顺序,相邻的两个cow运送顺序是否调换是可以证明与ti/di有关,从而按ti/di排序后的运送顺序是最优的(因为对于一种运送顺序,可以把ti/di最小的cow不断往前移而答案不增,依此类推)

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 #include <vector>
 5 using namespace std;
 6 #define ll long long
 7 #define pii pair<ll, ll>
 8 #define fi first
 9 #define se second
10 
11 int n;
12 ll sumd, res;
13 pii p[100005];
14 
15 bool cmp(pii p1, pii p2) {
16     return (double)p1.fi / p1.se < (double)p2.fi / p2.se;
17 }
18 
19 int main() {
20     scanf("%d", &n);
21     for (int i = 0; i < n; i++) {
22         scanf("%lld %lld", &p[i].fi, &p[i].se);
23         p[i].fi *= 2;
24         sumd += p[i].se;
25     }
26     sort(p, p + n, cmp);
27     for (int i = 0; i < n; i++) {
28         sumd -= p[i].se;
29         res += sumd * p[i].fi;
30     }
31     printf("%lld\n", res);
32 }
View Code

END

猜你喜欢

转载自www.cnblogs.com/hs-zlq/p/12169154.html