一、前言
🔥 之前在这篇文章里 【算法/训练】:贪心(算法理论学习及实践) 讲了贪心的知识,以及对于其的练习,这里的话我们就纯练习题目了,也是对之前的文章的补充,以后有关于贪心算法的题目也基本会放到这篇博客里面的
点击题目链接 题目描述
输入
输出 输出一个数表示组成的最大整数。
样例输入
3
121 12 96
样例输出
9612121
贪心策略
我们不考虑整体的最大,先考虑局部的最大,假如有 A B 两个整数,要使 A B 组合起来最大的话,则需要判断一下
#include<iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(const string& a, const string& b)
{
return a + b > b + a;
}
int main()
{
vector<string> arr;
string s;
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> s;
arr.push_back(s);
}
sort(arr.begin(), arr.end(),cmp);
for (int i = 0; i < n; i++) {
cout << arr[i];
}
return 0;
}
点击题目链接 题目描述
输入
输出
样例输入
179566
4
样例输出
15
贪心策略
#include<iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
string n;
int s;
cin >> n >> s;
for (int i = 0; i < s; i++) {
int j = 0;
while (n[j + 1] && n[j] <= n[j + 1]) j++;
while (n[j]) { // 字符串前移
n[j] = n[j + 1];
j += 1;
}
}
for (int i = 0, f = 1; n[i]; i++) {
if (n[i] == '0' && f) continue; // 找第一个非0数
cout << n[i];
f = 0;
}
return 0;
}
点击题目链接 题目描述
输入
输出
样例输入
100
9
90 20 20 30 50 60 70 80 90
样例输出
6
贪心策略
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
int w, n;
cin >> w >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
int l = 0, r = n - 1;
int cnt = 0; // 独木舟数量
while (l < r){
if (a[l] + a[r] <= w) l++;
r--; // 先把重的安排出去
cnt++;
}
if (l == r) cnt++; // 刚好有一个人单独的话
cout << cnt << endl;
return 0;
}
点击题目链接 题目描述
输入
输出
样例输入
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
样例输出
15
这个题目我们先分析一下,假如我们现在确定了子矩阵的起始行和终止行,那么我们要在当前子矩阵中找最大的子矩阵(行确定了,只需要确定拿几列的子矩阵最大就行),那么就需要对每一行进行相加,将这个子矩阵就变成了一维的子序列,那么现在就变成求出当前子序列的最大子序和 而最大子序和问题的贪心策略如下:
上面的起始行和终止行我们通过 枚举 来获取,而每一列的和值通过前缀和进行运算,这个方便获取起始行到终止行的列和值
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> arr(n + 1, vector<int>(n + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> arr[i][j];
arr[i][j] += arr[i - 1][j]; // 前缀和
}
}
int res = INT32_MIN; // 记录最大值
for (int i = 1; i <= n; i++) {//起始行
for (int j = i; j <= n; j++) { //终止行(注意:终止行一定要 >= 起始行)
int s = 0;
for (int k = 1; k <= n; k++) { // 起始行到终止行每列
int a = arr[j][k] - arr[i - 1][k]; // 差分
if (s >= 0) s += a;
else s = a;
if (s > res) res = s;
}
}
}
cout << res << endl;
return 0;
}
上面
可以表示以 i 位置为结尾的最大子序和,i 位置的值为
,则
那么为啥
表示这个最大子序和呢? 证明如下:
肯定是以 i 位置结尾的最大值
要么是
,要么就是
+
,此时获得
肯定也是最大的,同理往后推获得肯定是以 i 位置结尾的最大值
表示以 i 位置结尾的 最大子序和,那么
=
,或者等于
,但是如果我们的
< 0的话,后者一定是比前者小的,毕竟 + 一个负数肯定是变小,总不可能变大吧
点击题目链接 题目描述
输入
)
输出
样例输入
2 10 2
样例输出
3
贪心策略
#include <iostream>
using namespace std;
int main()
{
long long a, b, k; // 注意数据范围
cin >> a >> b >> k;
if (k == 0) {
if (b == 0) cout << 0 << endl;
else cout << b - a << endl;
}
else if (k == 1) {
cout << b - a << endl;
}
else {
long long ans = 0;
while (1) {
if (a * k <= b) {
ans += 1 + b % k;
b /= k;
}
else {
ans += b - a;
break;
}
}
cout << ans << endl;
}
return 0;
}
对上面贪心策略的理解,首先我们需要知道两个结论,如下:
操作肯定是优于 +1 操作,这个是肯定的吧,毕竟
达到
只需要一步,而+1 到达
需要
步
操作
,得到 140,同样再 +1 +1 +1 ,再
就可以得到 b了
点击题目链接 题目描述
,
)。有一种雷达,能探测到的范围为以 d 为半径的圆。问海岸线上至少造多少雷达可以把所有的小岛都处在探测范围内。注意雷达是建在海岸线上的,也就是x轴上的。
输入
,
≤1000)
输出
样例输入
3 2
1 2
-3 1
2 1
样例输出
2
这个题目我们先分析一下,这个题目其实并不是圆圈范围问题的,而是一个 数据映射 的问题,下面画个图理解一下:
贪心策略
,
] 结束位置从小到大排序,雷达放在区间末尾,pos 代表最后一个雷达的位置,若 pos <
,雷达数量 +1,此时pos =
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
typedef struct Node {
double l, r; // 存储每个岛屿雷达区间
};
bool cmp(const Node& a, const Node& b) {
return a.r < b.r;
}
int main()
{
int n;
double d;
cin >> n >> d;
double X, Y;
vector<Node> arr(n);
for(int i = 0; i < n; i++){
cin >> X >> Y;
if (Y > d) {
cout << -1 << endl;
return 0;
}
// 将坐标转化为范围
double t = sqrt(d * d - Y * Y);
arr[i].l = X - t;
arr[i].r = X + t;
}
sort(arr.begin(), arr.end(), cmp);
int ans = 1;
double pos = arr[0].r;// pos 记录最后一个雷达放置位置
for (int i = 1; i < n; i++) {
if (arr[i].l > pos) {
ans++;
pos = arr[i].r;
}
}
cout << ans << endl;
return 0;
}
点击题目链接 题目描述
输入
输出
样例输入
5
1 10
2 4
3 6
5 8
4 7
样例输出
4
1
2
3
2
4
贪心策略
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
struct Node {
int l, r;
int id; // 奶牛原有编号,防止排序改变
}arr[N];
bool cmp(const Node& a, const Node& b) {
if (a.l != b.l) return a.l < b.l;
return a.id < b.id;
}
int m_time[N], ans[N];
int cnt = 0;
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++){
cin >> arr[i].l >> arr[i].r;
arr[i].id = i;
}
sort(arr, arr + n, cmp);
int cnt = 0;
for (int i = 0; i < n; i++) {
int pos = -1;
for (int j = 0; j < cnt; j++) {
if (m_time[j] < arr[i].l) {
pos = j;
break;
}
}
if (pos == -1) pos = (cnt++);
m_time[pos] = arr[i].r;
ans[arr[i].id] = pos + 1;
}
printf("%d\n", cnt);
for (int i = 0; i < n; i++) {
printf("%d\n", ans[i]);
}
return 0;
}
点击题目链接 题目描述
输入
输出
样例输入
3 2
3 10
2 5
1 5
6 2
4 1
样例输出
2
分析:
贪心策略
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
struct Data {
int a, b;
};
Data cow[N], item[N];
bool cmp(const Data& x, const Data& y) {
if (x.b != y.b) return x.b < y.b;
return x.a < y.a;
}
int main()
{
int C, L;
cin >> C >> L;
for (int i = 0; i < C; i++) cin >> cow[i].a >> cow[i].b;
for (int i = 0; i < L; i++) cin >> item[i].b >> item[i].a; // 方便与奶牛共用一个排序函数,减少冗余代码
sort(cow, cow + C, cmp);
sort(item, item + L, cmp); // 防晒霜根据阳光强度排
int ans = 0;
for (int i = 0; i < C; i++) {
int f = 0;
for (int j = 0; j < L && !f; j++) {
if (item[j].a == 0) continue; // 第 j 个防晒霜数量
if (item[j].b <= cow[i].b && cow[i].a <= item[j].b) {
f = 1;
item[j].a--;
}
}
ans += f;
}
cout << ans << endl;
return 0;
}
点击题目链接 题目描述
并且需要一定的时间
完成。
+2∗
的报酬。求今天公司最多能获得的报酬。
输入
输出
样例输入
1 2
100 3
100 2
100 1
样例输出
1 50004
分析:
+2∗
,可以知道
与
的权重差很多,再结合数据范围 0 <
< 1400,0 <
< 100,因此对于两个任务 (
,
) 和 (
,
),如果存在
>
,那么选任务 (
,
) 肯定报酬更大
贪心策略
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
struct Data {
int x, y;
};
Data task[N], mach[N];
bool cmp(const Data& a, const Data& b) {
if (a.x != b.x) return a.x > b.x;
return a.y > b.y;
}
// 难度系数为 i 的机器数量
int cnt[105] = { 0 };
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%d%d", &mach[i].x, &mach[i].y);
for (int i = 0; i < m; i++) scanf("%d%d", &task[i].x, &task[i].y);
sort(mach, mach + n, cmp);
sort(task, task + m, cmp);
long long ans = 0, t_cnt = 0;
for (int i = 0, j = 0; i < m; i++) {
while(j < n && mach[j].x >= task[i].x) { // 机器最大工作时间 > 任务需要时间
cnt[mach[j].y] += 1;
j += 1;
}
for (int y = task[i].y; y <= 100; y++) {
if (cnt[y] == 0) continue;
cnt[y] -= 1;
ans += 500 * task[i].x + 2 * task[i].y;
t_cnt += 1;
break;
}
}
printf("%lld %lld\n", t_cnt, ans);
return 0;
}
点击题目链接 题目描述
。
。求染完所有节点所需的最小代价。
输入
≤500)
输出
样例输入
5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
样例输出
33
思路:
即可
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10;
int n, r;
int C[N], fa[N] = {0};
bool vis[N]; // 表明节点是合并过的
long long ans = 0;
int cnt[N]; // 每个节点包含原始节点数量
double w[N];// w 表示当前节点所包含节点平均值
int find_max() {
int x = -1;
for (int i = 1; i <= n; i++) {
if (i == r || vis[i] == true) continue;
if (x == -1 || w[x] < w[i]) x = i;
}
return x;
}
int find_fa(int x) {
if (vis[fa[x]]) return find_fa(fa[x]);
return fa[x];
}
int main()
{
cin >> n >> r;
for (int i = 1; i <= n; i++) {
cin >> C[i];
ans += C[i];
fa[i] = i; // 初始化 只有根节点父节点是自己
w[i] = C[i];
cnt[i] = 1;
}
for (int i = 1, a, b; i < n; i++){
cin >> a >> b; // 读入每条边信息
fa[b] = a;
}
// 将每个节点与其父节点合并
for (int i = 1; i < n; i++) {
int x = find_max(); //找权值最大的
int fa_x = find_fa(x); // 查找 x 第一个没有被合并的节点
ans += cnt[fa_x] * C[x];
C[fa_x] += C[x];
cnt[fa_x] += cnt[x];
w[fa_x] = 1.0 * C[fa_x] / cnt[fa_x];
vis[x] = true;
}
cout << ans << endl;
return 0;
}