2.2 一往直前!贪心法
区间
POJ 2376 给出N个区间,要求最小数量的区间能覆盖[1,T]
假设从左往右覆盖区间,当前已覆盖至[1,t],则贪心地从剩余区间中左端点≤t+1的选取右端点最大的。因为右端点越大,覆盖的范围越大。
因此,对所有区间按左端点排序,扫一遍模拟选取过程即可

1 #include2 #include 3 #include 4 using namespace std; 5 #define pii pair 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 }
POJ 1328 给出n个岛屿坐标,要在X轴上用尽量少的灯塔覆盖到这些岛屿
用岛屿坐标找灯塔的区间,代表在这一段区间里至少要有一个灯塔。对这些区间按右端点排序。从左往右扫,灯塔尽可能往后放,这样后面的区间被满足的可能性会更大

1 #include2 #include 3 #include 4 #include 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 }
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 #include2 #include 3 #include 4 #include <set> 5 #include 6 using namespace std; 7 #define pii pair 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 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 ::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 }
考虑区间的贪心的思路,主要往排序上想,哪怕不知道怎么贪,也可以先假设按左端点或者右端点排序,再假设怎么选取,其实可能的方案也不多,能解释样例和一些特殊情况之后就可以考虑怎么严格证明。证明的思路主要是,“我这样选了”会比“不这样选”优越在哪里。
其他
POJ 2393 给出N周生产奶酪的单价和需要卖出的量,另外奶酪可以储存起来留着下周卖,每周每单位奶酪储存费为S,要最小化总费用
维护一个top变量代表当前周的最低价格

1 #include2 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 }
POJ 1017 给出1×1到6×6种尺寸的包裹的个数,用尽量少的6×6箱子装起来
统计装3×3~5×5的包裹的箱子的空位,分成2×2的数量和1×1的数量,并尽可能转化为前者。然后就可以计算2×2和1×1是否还需要空间

1 #include2 #include 3 #include 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 }
POJ 3040 给出N种面值的硬币及其数量(≤1e9),并且这些面值是互相整除。每周需要支付至少C的津贴,问最多能支付几周
有一定难度的好题。当要从剩下的钱中凑一个C出来时,总是贪心地优先选大的面额。(因为不选这个面额改用小面额时,这些小面额加起来一部分还是等于这个面额,但“灵活性”变差了)并且计算出能否以此取法重复多次,不然会T。每一次凑面额的时候至少有一种面额会“用尽”,复杂度O(N^2) (复杂度还需考虑……)

1 #include2 #include 3 #include 4 #include 5 #include 6 using namespace std; 7 #define pii pair 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 ()); 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 }
POJ 1862 有n个stripies,权值wi,两两结合成2×sqrt(wi×wj) ,问最小的可能结果
考虑最后答案式,会发现结合次数越多套的根号越多,所以让权值大的先结合,把权值降下来。又由于两个结合后权值大于原来第二大的权值,所以加入新集合后一定是最大的,可以直接让它和剩下的第二大结合。

1 #include2 #include 3 #include 4 #include 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 }
POJ 3262 N头cow,每秒吃Di花,农夫需要2Ti时间把一只cow运回窝。求最少损失多少花
考虑对于一种运送顺序,相邻的两个cow运送顺序是否调换是可以证明与ti/di有关,从而按ti/di排序后的运送顺序是最优的(因为对于一种运送顺序,可以把ti/di最小的cow不断往前移而答案不增,依此类推)

1 #include2 #include 3 #include 4 #include 5 using namespace std; 6 #define ll long long 7 #define pii pair 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 }
END