跳到主要内容

1 - 贪心入门

贪心问题入门。

区间问题

区间选点

905. 区间选点 - AcWing题库

确定贪心算法:

  1. 将每个区间按照右端点从小到大排序。
  2. 从前往后一次枚举每个区间:
    1. 当前区间已经包含点,直接枚举下个区间
    2. 否则选择当前区间的右端点

证明贪心算法正确性:

  • 对于要的答案ans,必然是可行解cnt的最小值,有ans <= cnt

  • 对于极端情况(区间无交集),至少需要cnt才是可行解,有ans >= cnt

因此有ans = cnt,即贪心算法的局部最优解就是全局最优解。

参考代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Seg
{
int left, right;

bool operator< (const Seg& rhs) const
{
return right < rhs.right;
}

Seg(int left, int right) : left(left), right(right) {}
};

int main()
{
int n;
cin >> n;

vector<Seg> segs;
for (int i = 0; i < n; ++i)
{
int left, right;
cin >> left >> right;
segs.emplace_back(left, right);
}

// 贪心算法
sort(segs.begin(), segs.end());
int ans = 0, pos = -2e9;
for (auto& seg : segs)
{
if (pos < seg.left)
{
pos = seg.right;
++ans;
}
}
cout << ans;
}

最大不相交区间数量

908. 最大不相交区间数量 - AcWing题库

和上题一模一样。

区间分组

906. 区间分组 - AcWing题库

确定贪心算法:

  1. 将每个区间按照左端点从小到大排序。

  2. 从前往后一次枚举每个区间:

    判断能否将其放到某个现有的组中(当前区间左端点 <= 组中最右端点):

    • 如果不存在这样的组,就开个新组,将其放进新组
    • 如果存在这样的组,将其放入这个组,然后更新这个组的最右端点

参考代码如下:

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

struct Seg
{
int left, right;

bool operator< (const Seg& rhs) const
{
return left < rhs.left;
}

Seg(int left, int right) : left(left), right(right) {}
};

const int INF = 2e9;

int main()
{
int n;
cin >> n;

vector<Seg> segs;
for (int i = 0; i < n; ++i)
{
int left, right;
cin >> left >> right;
segs.emplace_back(left, right);
}

// 贪心算法
sort(segs.begin(), segs.end());
priority_queue<int, vector<int>, greater<int>> maxR;
for (auto& seg : segs)
{
if (maxR.empty() || maxR.top() >= seg.left)
maxR.push(seg.right);
else
{
maxR.pop();
maxR.push(seg.right);
}
}
cout << maxR.size();
}

区间覆盖

907. 区间覆盖 - AcWing题库

令被覆盖的区间范围为[start, end],确定贪心算法:

  1. 将每个区间按照左端点从小到大排序。

  2. 从前往后依次枚举每个区间:

    在所有能覆盖start的区间中,选择右端点最大的区间。

    选完后将start更新为该区间的右端点。

参考代码如下:

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

struct Seg
{
int left, right;

bool operator< (const Seg& rhs) const
{
return left < rhs.left;
}

Seg(int left, int right) : left(left), right(right) {}
};

const int INF = 2e9;

int main()
{
int st, ed, n;
cin >> st >> ed >> n;

vector<Seg> segs;
for (int i = 0; i < n; ++i)
{
int left, right;
cin >> left >> right;
segs.emplace_back(left, right);
}

// 贪心算法
sort(segs.begin(), segs.end());
int ans = 0;
bool isOK = false;
for (int i = 0; i < n; ++i)
{
int j = i, maxR = -INF;
while (j < n && segs[j].left <= st)
{
maxR = max(maxR, segs[j].right);
j++;
}

// 无解
if (maxR < st)
{
isOK = false;
break;
}

// 有解
ans++;
if (maxR >= ed)
{
isOK = true;
break;
}

st = maxR;
i = j - 1;
}

if (isOK)
cout << ans;
else
cout << -1;
}

Huffman树

获取答案的过程是一棵Huffman树:

合并果子

148. 合并果子 - AcWing题库

只需要每次合并重量最小的两堆果子就行了。

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
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 ans = 0;
while (heap.size() > 1)
{
int a = heap.top(); heap.pop();
int b = heap.top(); heap.pop();
ans += a + b;
heap.push(a + b);
}
cout << ans;
}

排序不等式

形如f(x) = e1 * w1 + e2 * w2, ...的最值问题。

排队打水

913. 排队打水 - AcWing题库

让耗费时间少的人先打水,这样花费所有人的时间会最少:

  • 时间为1的人打水,剩余人等待 6 * 1;
  • 时间为2的人打水,剩余人等待 5 * 2;
  • 时间为3的人打水,剩余人等待 4 * 3;
  • ...
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
int n;
cin >> n;
vector<int> t(n);
for (int i = 0; i < n; ++i)
cin >> t[i];

sort(t.begin(), t.end());

long long res = 0;
for (int i = 0; i < n; ++i)
res += t[i] * (n - i - 1);

cout << res;
}

绝对值不等式

形如f(x) = |x1 - x| + |x2 - x| + ...的最值问题。

可以将这些选项分组,有:

f(x) = (|x1 - x| + |xn - x|) + (|x2 - x| + |xn-1 - x|) + ...

如果我们要求f(x)的最小值,例如第一组,x都应尽量位于[x1, xn]之间,此时每组路径和为xn - x1。最稳妥的做法就是取它们的中位数。

货舱选址

104. 货仓选址 - AcWing题库

对商店从小到大排序后,取它们中点即可,然后求距离之和。

参考代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i)
cin >> a[i];

sort(a.begin(), a.end());

int res = 0;
for (int i = 0; i < n; ++i)
res += abs(a[i] - a[n / 2]);
cout << res;
}

推公式

题中推出来的公式 + 不等式解决贪心问题。

耍杂技的牛

125. 耍杂技的牛 - AcWing题库

要使每头牛的危险值最小,根据贪心思想:

  • 自身w值越大应该放到底部(即减小上述式中的被减数)

  • 自身s值越大应该放到底部(即增大上述式中的减数)

因此按照 w[i] + s[i] 从小到大的顺序排序,最大的危险系数一定是最小的。

参考代码如下:

#include <iostream>
#include <algorithm>
using namespace std;

typedef pair<int, int> PII;
const int N = 50010;
int n;
PII cow[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
{
int s, w;
scanf("%d%d", &w, &s);
cow[i] = {w + s, w};
}

sort(cow, cow + n);

int res = -2e9, sum = 0;
for (int i = 0; i < n; i ++ )
{
int s = cow[i].first - cow[i].second, w = cow[i].second;
res = max(res, sum - s);
sum += w;
}

printf("%d\n", res);

return 0;
}