第十二章 贪心
AcWing 1521. 魔术卷
问题描述
-
问题链接:AcWing 1521. 魔术卷、原题链接
分析
-
本题相当于给我们两个数组,我们需要给两个数组中的元素配对,使得配对的元素之积之和最大。
-
对于两个数组中的正数,排序后,第一个数组中的最大值和第二个数组中的最大值配对,第一个数组中的次大值和第二个数组中的次大值配对,…,这样得到的和最大;负数类似。
-
相关数学知识:排序不等式,即:反序和 ≤ 乱序和 ≤ 顺序和。
代码
- C++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
scanf("%d", &m);
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
sort(a, a + n);
sort(b, b + m);
int res = 0;
for (int i = 0, j = 0; i < n && j < m && a[i] < 0 && b[j] < 0; i++, j++)
res += a[i] * b[j];
for (int i = n - 1, j = m - 1; i >= 0 && j >= 0 && a[i] > 0 && b[j] > 0; i--, j--)
res += a[i] * b[j];
printf("%d\n", res);
return 0;
}
AcWing 1522. 排成最小的数字
问题描述
-
问题链接:AcWing 1522. 排成最小的数字、原题链接
分析
-
本题和Leetcode 0179 最大数、剑指 Offer 45 把数组排成最小的数一样,在于
LC179
求最大值,Offer 45
求最小值。分析过程是一样的,下面以求最大值分析为例。 -
分析如下:
代码
- C++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010;
int n;
string str[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> str[i];
sort(str, str + n, [](const string &a, const string &b){
return a + b < b + a;
});
string res;
for (auto &p : str) res += p;
int k = 0;
while (k + 1 < res.size() && res[k] == '0') k++;
printf("%s\n", res.substr(k).c_str());
return 0;
}
AcWing 1553. 用 Swap(0, i) 操作进行排序
问题描述
分析
-
本题是环状图的问题,对于每个数
id
,让其指向其下标。这样我们可以得到一个图,这个图很有特点,一定是由若干环组成的。 -
下列演示了不同操作的影响:
-
我们最终的目标是将所有的点变为自环。
-
通过和
0
交换,可以分为两大类操作:(1)
0
与环内的点交换:① 和0
后面一个点next
交换,会让next
变为自环,有效操作;② 和非0
后面的一个点交换位置,会让0
所在的环变为两个环,操作无意义;(2)
0
和环外的点交换:合成一个环,有效操作。 -
具体操作时,可以先找到
0
所在的环,让0
和后面的一个点交换,让所有该环内的点变为自环;然后找到其他环,首先和0
合并,然后再将合并后的环中的每个点变为自环。
代码
- C++
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int p[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int id;
scanf("%d", &id);
p[id] = i;
}
int res = 0;
for (int i = 1; i < n; ) {
while (p[0]) swap(p[0], p[p[0]]), res++;
while (i < n && p[i] == i) i++;
if (i < n) swap(p[0], p[i]), res++;
}
printf("%d\n", res);
return 0;
}
AcWing 1556. 月饼
问题描述
-
问题链接:AcWing 1556. 月饼、原题链接
分析
- 优先选择单位价值较大的月饼卖出即可。
代码
- C++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
double m;
struct Cake {
double p, w;
bool operator< (const Cake &t) const {
return w / p > t.w / t.p;
}
} c[N];
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> c[i].p;
for (int i = 0; i < n; i++) cin >> c[i].w;
sort(c, c + n);
double res = 0;
for (int i = 0; i < n && m > 0; i++) {
double r = min(m, c[i].p);
m -= r;
res += c[i].w / c[i].p * r;
}
printf("%.2lf\n", res);
return 0;
}
AcWing 1603. 整数集合划分
问题描述
-
问题链接:AcWing 1603. 整数集合划分、原题链接
分析
- 分类讨论:当有偶数个数据,则分成两个数据个数相同的集合,较大的数分到一个集合,较小的数分到另一个集合;当有奇数个数据,因为给定的数据都是正整数,分成两个集合,一个集合
n/2+1
个数据,这个集合放较大的数据,另一个集合放n/2
个数据,放较小的数据。
代码
- C++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int w[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &w[i]);
sort(w, w + n);
int s1 = 0, s2 = 0;
for (int i = 0; i < n / 2; i++) s1 += w[i];
for (int i = n / 2; i < n; i++) s2 += w[i];
printf("%d %d\n", n % 2, s2 - s1);
return 0;
}
AcWing 1618. 结绳
问题描述
-
问题链接:AcWing 1618. 结绳、原题链接
分析
-
本题的考点:哈夫曼树。
-
本题如果将两个绳子(长度分别为
a、b
)合并成一条绳子,则新的绳子长度为(a+b)/2
。我们的目标是最终拼接成一条绳子的长度最大值。 -
应该先将长度最小的两个绳子合并。因为绳子越靠前被合并,被折叠的次数越多,我们希望较长的绳子折叠的次数尽可能少,所以较长的绳子应该较后备拼接。
-
首先我们可以按照绳子的长度升序排序,然后从前向后合并绳子,合并后的绳子的长度一定是所有绳子中的长度最小的,这是因为
(a+b)/2 <= max(a, b)
。
代码
- C++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010;
int n;
double w[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> w[i];
sort(w, w + n);
for (int i = 1; i < n; i++) w[0] = (w[0] + w[i]) / 2;
printf("%d\n", (int)w[0]);
return 0;
}
AcWing 1517. 是否加满油
问题描述
-
问题链接:AcWing 1517. 是否加满油、原题链接
分析
-
首先按照距离起点的距离将加油站排序。
-
考虑第一站(位置
0
处)是否存在加油站,如果不存在,则无法到达目的地。 -
接着依次考虑每个加油站是否应该加油以及如果应该加油应该加多少油。
-
当前加了一些油的加油站编号如果为
i
,考虑其如果在本站加满油可以到达的所有加油站,找出油价最低的一个加油站,假设编号为k
。 -
如果加油站
k
的油价低于加油站i
的价格,则在加油站i
加一些油满足可以开到加油站k
即可,然后考虑加油站k
;否则直接在加油站i
加满油即可,然后考虑加油站k
。
代码
- C++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int c_max, d, d_avg, n; // 油箱最大容量,目标距离起点距离,单位汽油里程数,加油站总数
struct Stop {
double p, d; // 汽油单位价格,距离起点距离
bool operator< (const Stop &t) const {
return d < t.d;
}
} s[N];
int main() {
cin >> c_max >> d >> d_avg >> n;
for (int i = 0; i < n; i++) cin >> s[i].p >> s[i].d;
s[n] = {0, (double)d}; // 终点
sort(s, s + n);
if (s[0].d) { // 说明起点不存在加油站
puts("The maximum travel distance = 0.00");
return 0;
}
double res = 0, oil = 0; // 花费,每站剩余油量
double d_max = c_max * d_avg; // 装满油箱可以行驶的距离
for (int i = 0; i < n; ) {
int k = -1;
for (int j = i + 1; j <= n && s[j].d - s[i].d <= d_max; j++)
if (s[j].p < s[i].p) { // 找到一个比当前所在加油站单价更低的加油站
k = j;
break;
} else if (k == -1 || s[j].p < s[k].p) {
k = j;
}
if (k == -1) { // 说明在当前加油站i加满油也无法到达下一个加油站
printf("The maximum travel distance = %.2lf\n", s[i].d + d_max);
return 0;
}
if (s[k].p < s[i].p) { // 此时在加油站i加一些油足够到加油站k即可
res += ((s[k].d - s[i].d) / d_avg - oil) * s[i].p;
oil = 0;
i = k;
} else { // 说明在当前加油站i加满油可以达到的加油站的油价都比当前油价高
res += (c_max - oil) * s[i].p;
oil = c_max - (s[k].d - s[i].d) / d_avg;
i = k;
}
}
printf("%.2lf\n", res);
return 0;
}