贪心
贪心
贪心跟动态规划一样,没有模板,但比动态规划更狠的是,贪心没有套路。贪心一般很难,我们从小到大的考试当中,不可能在考场上去解决一个从来没有遇到过的问题,一般都是把某个问题转化为我们以前见过的某一类问题。不会去解决一个全新的问题。
考试一般是考查转化的能力,所以把所有的模型背下来其实也是可以的。
区间问题
贪心问题,区间问题无外乎就是排序
- 按左端点排序
- 按右端点排序
- 双关键字排序(先按右端点,再按左端点)
没有思路就先试一下,举一些例子,感受一下是不是有问题,看看有什么规律没有。
AcWing 905 区间选点
题目解析转载:
糖豆爸爸
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
struct Range{
int l;
int r;
bool operator< (const Range &W)const
{
return r < W.r;
}
}range[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> range[i].l >> range[i].r;
sort(range, range + n);
int res = 0;
int ed = -2e9;
for(int i = 0; i < n; i++)
{
if(range[i].l > ed)
{
res++;
ed = range[i].r;
}
}
cout << res << endl;
return 0;
}
AcWing 908 最大不相交的区间数量
代码跟上一题完全一样
题目解析:糖豆爸爸
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
struct Range{
int l, r;
bool operator< (const Range &W)const
{
return r < W.r;
}
}range[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> range[i].l >> range[i].r;
sort(range, range + n);
int res = 0;
int ed = -2e9;
for(int i = 0; i < n; i++)
if(range[i].l > ed)
{
res++;
ed = range[i].r;
}
cout << res << endl;
return 0;
}
AcWing 906 区间分组
该代码不同于前面两个题,该题是按左端点排序。
什么题用什么排序,还是得具体情况具体分析。先分析,找思路,再尝试。
题目解析:糖豆爸爸
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 100010;
struct Range{
int l;
int r;
bool operator< (const Range &W)const
{
return l < W.l;
}
}range[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> range[i].l >> range[i].r;
sort(range, range + n);
priority_queue<int, vector<int>, greater<int> > heap;
for(int i = 0; i < n; i++)
{
if(heap.empty() || range[i].l <= heap.top() )
heap.push(range[i].r);
else
{
heap.pop();
heap.push(range[i].r);
}
}
cout << heap.size() << endl;
return 0;
}
AcWing 907 区间覆盖
题目解析:糖豆爸爸
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
struct Range{
int l , r;
bool operator< (const Range &W)const
{
return l < W.l;
}
}range[N];
int main()
{
int st, ed;
cin >> st >> ed;
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> range[i].l >> range[i].r;
sort(range, range + n);
int res = 0;
bool success = false;
for(int i = 0 ; i < n; i++)
{
int j = i;
int r = -2e9;
//能覆盖st且右端点最大的区间
while(j < n && range[j].l <= st)
{
r = max(r, range[j].r);
j++;
}
//如果没有区间能覆盖st(两种情况)
// 1. 有区间 l < st, 但 r 也 < st
// 2. 没有区间 l < st, 即 r 没有改变,为 -2e9
if(r < st)
{
res = -1;
break;
}
res++;
if(r >= ed)
{
success = true;
break;
}
//更新
st = r;
i = j - 1;
}
if(success) cout << res << endl;
else cout << "-1" << endl;
return 0;
}
Huffman树
AcWing 148. 合并果子
题目解析:糖豆爸爸
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int main()
{
int n;
cin >> n;
priority_queue<int, vector<int>, greater<int> > heap;
for(int i = 0; i < n; i++)
{
int x;
cin >> x;
heap.push(x);
}
int res = 0;
while(heap.size() != 1)
{
int a = heap.top(); heap.pop();
int b = heap.top(); heap.pop();
heap.push(a + b);
res += (a + b);
}
cout << res << endl;
return 0;
}
排序不等式
AcWing 913. 排序打水
思路: 让最慢的人最后打水,谁快就谁先来。
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++) cin >> a[i];
sort(a, a + n);
LL res = 0;
for(int i = 0; i < n; i++)
res += a[i] * (n - i - 1);
cout << res << endl;
return 0;
}
绝对值不等式
AcWing 104 货仓选址
上图来自
Cloudeeeee
注意上图的 >=
,
将第二个右边等式 抽象为 (|a - x| + |b - x|),
当 x 位于所有情况的 a 与 b 之间时,>= 为 =,
(当 x 位于 某个 a的左边,或 b 的右边,则(|a - x| + |b - x|)> b - a,即 >= 的 = 不成立)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int a[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++) cin >> a[i];
sort(a, a + n);
int res = 0;
for(int i = 0; i < n; i++)
res += abs(a[i] - a[n / 2]);
cout << res << endl;
return 0;
}
推公式
AcWing 125 耍杂技的牛
解释公式
以下图片来自www.acwing.com算法基础课
如果 wi + si > w(i + 1) + s(i +1),(即有两个不符合要求)
我们只交换第 i 个位置和第 i + 1 个位置的牛,对于其他牛的危险值是不变的。
关于右上角的转换
2. 因为我们只交换第 i 个位置和第 i + 1 个位置的牛,对于其他牛的危险值是不变的。
则,
3. 各项加上 s(i) + s(i + 1), 大小关系不变
好,s(i) + s(i + 1), w(i) + w(i + 1)都是整数,
wi + si > si;
wi + si > w(i + 1) + s(i + 1)(已经假设了)
那么不管 s(i + 1)为何值,我们可以知道
max( s(i + 1),(wi + si )) > max( s(i),(w(i + 1) + s(i + 1) ))
也就是说交换前的 max值大于交换后的 max值,但显然,我们想要是更小的 max值,即交换后的 max值,这说明交换后是更优的,
即 w(i + 1) + s(i + 1) > wi + si
所以 按 wi + si 从小到大的顺序排,最大的危险系数一定是最小的
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 50010;
typedef pair<int, int> PII;
PII cow[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
int w, s;
cin >> w >>s;
cow[i] = {w + s, s}; // w + s 是为了排序用
}
sort(cow, cow + n);
int res = -2e9;
int num = 0;
for(int i = 0; i < n; i++)
{
int w = cow[i].first - cow[i].second;
int s = cow[i].second;
res = max(res, num - s);
num += w;
}
cout << res << endl;
return 0;
}
AcWing 114. 国王游戏
这里加一道同类题。
AcWing 114. 国王游戏
题目解析:
类比上一题,我们按 ai + bi 从小到大的顺序排,最后的答案符合要求,
与上题类似,我们可以假设 ai * bi > a(i+1) * b(i+1),最后可以证明得出交换后的答案更优,即可得证。
该题 数据相乘后明显非常大,所以我们要用到高精度。
解释:vector<int>(a.rbegin(), a.rend()) > vector<int>(b.rbegin(), b.rend())
这行代码比较了两个 vector 对象 a 和 b,具体比较方式是将它们反转后再逐位比较大小。
其中,a.rbegin() 和 a.rend() 表示 a 向量的反转迭代器(即从 a 向量末元素开始的正向迭代器)和正向迭代器(指向 a 向量的开头);b.rbegin() 和 b.rend() 则表示 b 向量的反转迭代器和正向迭代器。vector(a.rbegin(), a.rend()) 和 vector(b.rbegin(), b.rend()) 分别表示两个反转后的向量,且两者具有相同的元素序列。
根据 C++ 的容器比较规则,当比较运算符应用于两个容器对象时,容器中的元素会逐个进行比较,直到遇到一个相对大小的差异或者其中一个容器被耗尽为止。在此例中,会先将两个容器反转,然后从最后一个元素开始按位比较它们的大小,直到找到第一个不同的元素,或者一个容器已经被耗尽。如果 a 向量的第 i 个元素是大于 b 向量的第 i 个元素,则返回 true,否则返回 false。
需要注意的是,这种比较方式会忽略正向的元素顺序,因此可能会对某些应用场景下的正确性产生影响。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1010;
typedef pair<int, int> PII;
PII person[N];
vector<int> mul(vector<int> a, int b)
{
int t = 0;
vector<int> c;
for(int i = 0; i < a.size(); i++)
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while(t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
vector<int> div(vector<int> a, int b)
{
int t = 0;
vector<int> c;
for(int i = a.size() - 1; i >= 0; i--)
{
t = t * 10 + a[i];
c.push_back(t / b);
t %= b;
}
reverse(c.begin(), c.end());
while(c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
vector<int> max_vec(vector<int> a, vector<int> b)
{
if(a.size() > b.size()) return a;
if(b.size() > a.size()) return b;
if(vector<int>(a.rbegin(), a.rend()) > vector<int>(b.rbegin(), b.rend()))
return a;
return b;
}
int main()
{
int n;
cin >> n;
for(int i = 0; i <= n; i++)
{
int a, b;
cin >> a >> b;
person[i] = {a * b, a};
}
sort(person + 1, person + n + 1);
vector<int> num(1, 1);
vector<int> res(1, 0);
for(int i = 0; i <= n; i++)
{
int a = person[i].second, b = person[i].first / person[i].second;
if(i) res = max_vec(res, div(num, b));
num = mul(num, a);
}
for(int i = res.size() - 1; i >= 0; i--)
cout << res[i];
cout << endl;
return 0;
}
时间复杂度
比赛时, 时间复杂度一般为 1 s 内,
即 107 ~ 108 都是OK的,
如果超过108,常数不是特别小的话,就会超时。
比赛时,空间复杂度一般不用考虑
限制一般为 64MB
即 64MB = 1.6 * 107 字节