贪心专题1
简介
贪心是有一种由局部最优解推算到全局最优解的思想, 在做贪心题的时候最重要的一点就是证明为什么这个局部最优可以推出全局最优, 所以贪心题往往是较考验思维的。
P1223 排队接水
分析
这题就不用多讲了吧, 一道小学数学题
如果我没记错的话, 小学时候是以轮船运货物为背景出的题
结论
直接升序排列 总体等待的时间就是最小的, 即平均时间最小;
证明
在计算是越往前需要被乘上更大的系数, 显然是从前往后升序总体,最小。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
typedef pair<int, int> PII;
#define x first
#define y second
PII q[N];
int n;
int main() {
cin >> n;
int x;
for (int i = 0; i < n; ++ i)
cin >> x, q[i] = { x, i + 1 };
sort(q, q + n);
for (int i = 1; i <= n; ++ i)
q[i].x += q[i - 1].x, cout << q[i - 1].y << " ";
puts("");
double sum = 0;
for (int i = 1; i < n; ++ i)
sum += q[i - 1].x;
printf("%.2f", sum / n);
return 0;
}
P1031 均分纸牌
分析
首先 可以发现 一定是存在一种方法能够用在 n - 1 次操作内使得数组所有的数相等
思路
我们将数组全部减去平均值 正数则代表该位置需要将牌移向别的位置
负数则代表该位置需要别的牌移向该位置
然后将该信息转移到下一个位置 代表该位置向左所以的节点需要的卡牌(可能为负)
当转移为0 的时候则代表从开始转移的l
到结束的r
中,存在一种方法使得其中卡牌完全相等
且改操作数一定等于区间长度 - 1
所以我们只需要遍历找到每一个这样的区间即可
证明
1
对于
且改操作数一定等于
区间长度 - 1
假设 :操作次数不需要 区间长度 - 1
那么必然存在至少一条路径没有经过
我们假设 n
不需要向n - 1
转移牌 那么说明n - 1
到 l
可以 通过某种操作将自己全变为平均数 这与定义(找到第一个不需要转移的点)矛盾。也就是这个区间其实是两个区间。
所以假设不成立
2
我们所有的一切都围绕着这一句话在进行
首先 可以发现 一定是存在一种方法能够用在 n - 1 次操作内使得数组所有的数相等
那么我们证明一下这个
假设 必须得 n - 1个方法以上
才能将数组全部变为一致, 那么由抽屉原理
可以得出 一定存在至少一条路径是被重复走了的, 我们假设 这条路 为 n
到 n - 1
(1) 加入两次数据的传输都是从n到n - 1 那么很明显我们一定可以合并这次传输 (2)假如我们是一次 n->n - 1 和一次 n - 1->n 我们也可以很明显的得出 这两条路劲中 至少有一条是多余的(比如 左往右传2
右往左传 3
等价于 右往左传1
)
所以显然 假设不成立
代码
O(n)
#include <iostream>
using namespace std;
const int N = 110;
int q[N];
int n;
int main() {
cin >> n;
int sum = 0;
for (int i = 0; i < n; ++i)
cin >> q[i], sum += q[i];
sum /= n;
for (int i = 0; i < n; ++i)
q[i] -= sum;
int res = 0;
for (int i = 0; i < n; ++ i)
if (q[i] != 0) q[i + 1] += q[i], res++; // 需要转移的时候直接加上就好了
cout << res;
return 0;
}
P3817 小A的糖果
分析
首先肯定每一个点都不能超过x 所以我们先将每个点 去掉大于x 的部分
从左往右考虑, 如果i 与 i +1 的位置发生了冲突 我们应该修改那个位置?
显然是i +1的位置 因为对于i 与 i - 1 一定是合法的了,修改了对后面没有任何的帮助, 而修改i +1 可以使 i +1 和 i +2 更接近合法
所以我们只需要每次修改右值即可得到最小值
证明 :
分析的思路即为证明 :
如果i 与 i +1 的位置发生了冲突 我们应该修改那个位置?
显然是i +1的位置 因为对于i 与 i - 1 一定是合法的了, 修改了对后面没有任何的帮助, 而修改i +1 可以使 i +1 和 i +2 更接近合法
代码
#include <iostream>
using namespace std;
const int N = 100010;
typedef long long LL;
int q[N];
int n, x;
LL res;
int main() {
cin >> n >> x;
for (int i = 0; i < n; ++ i) {
cin >> q[i];
res += max(0, q[i] - x);
q[i] = min(x, q[i]);
}
for (int i = 0; i < n - 1; ++ i) {
LL sum = q[i] + q[i + 1];
if (sum > x) res += sum - x, q[i + 1] -= sum - x;
}
cout << res;
return 0;
}
本次的贪心专题1
就到这里结束啦!, 完结撒花!!!