C++U4-02-贪心算法2
上节课作业部分(点击跳转)
[纪念品分组]
【算法分析】 贪心算法:先对数组从小到大排序,用 l=1, r=n 指针指向首尾元素;如果 pl+pr≤w,则将pl和pr分一组,指针 l++,r--。如果 pl+pr>w,则将 pr单独作为一组,指针 r--。如此反复直到取完所有元素。 #include <iostream> #include <algorithm> using namespace std; int w, n, ans; int p[30010]; int main() { cin >> w >> n; for (int i = 1; i <= n; i++) { cin >> p[i]; } int l = 1, r = n; //左右指针,指向首尾元素 sort(p + 1, p + n + 1); while (l <= r) { if (p[l] + p[r] <= w) { //物品i和物品j分一组 l++; r--; ans++; } else { //物品j单独一组 r--; ans++; } } cout << ans; return 0; }
[活动安排]
【算法分析】 当只有两个活动(两个时间段)的时候,选择参加哪个都是可以的,当有第三个活动的时候,这时候就会发现优先选择前两个结束较早的那个活动,才能完整的看第三个活动,所以尽早看完一个活动就有机会参加其它活动(也就是按结束时间从早到晚排序)。 #include <iostream> #include <algorithm> using namespace std; struct node { int s, e;//开始时间,结束时间 }a[1010]; bool cmp(node a, node b) { return a.e < b.e; } int main() { int n, num = 1; cin >> n; for (int i = 0; i < n; i++) cin >> a[i].s >> a[i].e; sort(a, a + n, cmp); int last = a[0].e; for (int i = 1; i < n; i++) { if (a[i].s >= last) { //下一个活动的开始时间大于等于下一个活动的结束时间 num++; last = a[i].e; } } cout << num; return 0; }
[排座椅]
本题目的是找到通道的最优位置,贪心算法思路是,选择隔开最多人的通道,记录输出出来;
代码思路是通过输入的交头接耳的同学的行列位置,就可以判断是横着聊天还是竖着聊天,可以选择用行还是列的通道隔开;然后就结构题数组中不断记录通道的编号和通道隔开的交头接耳的同学的对数,就可以记录哪个线隔开的人多,然后根据隔开的人排序,选出哪条线,然后再根据线的序号排序,因为题目要求小编号的线先输出。
【算法分析】 本题的贪心需要预处理下,定义结构体 Pos 存当前位置和当前位置能隔开的对数,开 2 个结构体一维数组,row[i].num 记录如果在第 i 行加通道,可以分割多少对调皮学生,col[j].num 记录如果在第 j 列加通道,可以分割多少对调皮学生。然后用 sort 排序,隔开对数多的放在前面,在此前提下,位置小的排到前面(输出的结果也要排序)。最后输出分割学生最多的前 k 行和前 l 列。 #include <iostream> #include <algorithm> using namespace std; const int N = 1e3 + 10; struct Pos { int pos, num; // 位置,隔开的对数 } row[N], col[N]; // 横向通道,纵向通道 bool cmp1(Pos a, Pos b) { return a.num > b.num; // 隔开对数多的放前面 } bool cmp2(Pos a, Pos b) { return a.pos < b.pos; // 位置小的排在前面 } int main() { int m, n, k, l, d; // m 行 n 列的教室,k 个横向通道,l 个纵向通道,d 对交头接耳的同学 cin >> m >> n >> k >> l >> d; for(int i = 1; i <= d; i++) { int x1, y1, x2, y2; // 会交头接耳的两个同学的位置 cin >> x1 >> y1 >> x2 >> y2; if(y1 == y2) { // 同一列 int x = min(x1, x2); row[x].pos = x; row[x].num++; // x 这条横向通道能隔开的对数加 1 }else if(x1 == x2) { // 同一行 int y = min(y1, y2); col[y].pos = y; col[y].num++; // y 这条纵向通道能隔开的对数加 1 } } sort(row + 1, row + m, cmp1); // 1~m-1 sort(col + 1, col + n, cmp1); // 1~n-1 sort(row + 1, row + k + 1, cmp2); // 1~k 按照位置升序 sort(col + 1, col + l + 1, cmp2); // 1~l 按照位置升序 for(int i = 1; i <= k; i++) { cout << row[i].pos << " "; } cout << endl; for(int i = 1; i <= l; i++) { cout << col[i].pos << " "; } return 0; }
凑零钱
雷达安装
【算法分析】 1.所有的雷达应该装在海岸线上,因为对于每一个不在海岸线上的雷达站 A,和它横坐标相同的海岸线上的雷达站 B 的海面探测范围一定包含 A 的探测范围 2.雷达的探测距离为 d,所以一个雷达想要探测到小岛,则必须和它的距离小于等于 d,用勾股定理推出探测到一个岛屿 (x,y) 的海岸线区间为 [x−sqrt(d∗d−y∗y),x+sqrt(d∗d−y∗y)],这样每个岛屿都对应一个区间,我们需要在 x 轴上取尽量少的点使每个区间上都至少有一个点,于是我们就把这道题转换成了一个区间取点问题。 3.把所有区间按右端点升序排序,如果右端点相同则按左端点降序排序,开始贪心。在第一个区间 [x,y] 取点时取第一个区间的右端点一定最优。因为选的点在 [a,b) 中,包含此点的区间更多。 遍历所有区间,每次取当前区间的右端点建立雷达站,这个点的横坐标记为 now=a[1].r, 若 now 包含在当前区间的左端,则继续遍历下一个区间,否则 now 更新为当前区间的右端点,ans++;。 【参考代码】 #include <iostream> #include <cmath> #include <algorithm> using namespace std; struct Point { //区间结构体 double l, r; // }a[1010]; bool cmp(Point a, Point b) { //所有区间按右端点升序排序,如果右端点相同则按左端点降序排序,开始贪心 if (a.r < b.r) return true; else if (a.r == b.r) return a.l > b.l; else return false; } int main() { int n, d, ans = 1; cin >> n >> d; for (int i = 1; i <= n; i++) { int x, y; cin >> x >> y; if (y > d) { //无法覆盖 cout << "-1"; return 0; } double dis = sqrt(d * d - y * y); a[i].l = x - dis; a[i].r = x + dis; } sort(a + 1, a + n + 1, cmp); double now = a[1].r; for (int i = 2; i <= n; i++) { if (now < a[i].l) { now = a[i].r; ans++; } } cout << ans; return 0; }
作业部分:
【算法分析】 将小码君可以摘到的苹果所需要的力气值存入数组 tmp 中,贪心策略是优先摘所需要的力气值小的苹果。 【参考代码】 #include <iostream> #include <algorithm> using namespace std; int tmp[5010]; //可以摘到苹果需要的力气 int main() { int n, k, a, b; //苹果数 力气 椅子的高度 小码君手伸直最大的高度 cin >> n >> k; cin >> a >> b; int cnt = 0, t = 0; for (int i = 1; i <= n; i++) { int c, d; cin >> c >> d; //高度、力气 if (c <= a + b) { //小码君可以摘到的苹果 tmp[++cnt] = d; } } sort(tmp + 1, tmp + cnt + 1); for (int i = 1; i <= cnt; i++) { if (k - tmp[i] < 0) break; k -= tmp[i]; t++; } cout << t; return 0; }
【算法分析】 最优策略:每一次贪心地选当前单位重量价值最大的金属装入口袋。 【参考代码】 #include <iostream> #include <algorithm> using namespace std; struct node { int n; //重量 int v; //价值 double up; //单位重量价值 }; bool cmp(node x, node y) { return x.up > y.up; } int main() { int k; cin >> k; while (k--) { int w, s; double va = 0; //带走的总价值 node a[105]; cin >> w >> s; for (int i = 1; i <= s; i++) { cin >> a[i].n >> a[i].v; a[i].up = a[i].v * 1.0 / a[i].n; } sort(a + 1, a + s + 1, cmp); for (int i = 1; i <= s; i++) { if (w - a[i].n < 0) { va += a[i].up * w; break; } w -= a[i].n; va += a[i].v; } printf("%.2f\n", va); } return 0; }
【算法分析】 用两个数组 head 和 gold 分别保存龙的头的大小和骑士拥有的金币数,使用 sort 将两个数组排序。 i、j 初始化为 1,while循环条件为 i≤n 并且 j≤m ,在循环中: 如果第 i 个头能被第 j 个骑士砍掉,则累计花费的金币,砍下头的数量加 1,并且 i,j 都加 1。 否则换下一个骑士继续尝试。 最后如果砍下龙头的数量和 n 相等,输出最小花费;否则说明没有砍完,输出 you died!。 【参考代码】 #include <iostream> #include <algorithm> using namespace std; const int N = 2e4 + 10; int head[N]; // 龙的头的大小 int gold[N]; // 骑士拥有的金币数(砍掉的头的大小) int main() { int n, m; // n 个头,m 个骑士 cin >> n >> m; for(int i = 1; i <= n; i++) { cin >> head[i]; } for(int i = 1; i <= m; i++) { cin >> gold[i]; } sort(head + 1, head + n + 1); // 升序 sort(gold + 1, gold + m + 1); // 升序 int i = 1, j = 1; // 头,骑士 int sum = 0; // 最小花费 int cnt = 0; // 砍下的头数 while(i <= n && j <= m) { // 第 i 个头能够被第 j 个骑士砍掉 if(head[i] <= gold[j]) { sum += gold[j]; cnt++; // 砍下的头数加 1 i++; // 下一个头 j++; // 下一个骑士 }else { // 砍不掉 j++; // 下一个骑士继续尝试 } } if(cnt == n) cout << sum; // 砍完了,输出最小花费 else cout << "you died!"; return 0; }
第四题,均分纸牌查看视频:
【贪心算法,讲解,均分纸牌】 https://www.bilibili.com/video/BV1c7411d7pB/?share_source=copy_web&vd_source=a7ac0c0928d95f5dba9f91d92ac06af8