贪心算法的难点在于证明贪心的答案是最优解,我们用Ans代指最优解,Cnt代指贪心解。我们只要能证明两条,Ans <= Cnt,Ans >= Cnt。就能证明Ans = Cnt。
区间问题
区间选点
步骤只有两步,首先将区间按右端点从小到大排序,然后从前往后依次枚举每个区间,如果当前区间中已经包含点,则直接略过,否则选择当前区间的右端点。
证明贪心解为最优解的话,首先Ans <= Cnt是显而易见不需要证明的,然后我们将区间分开,可以发现我们会取没有交集的区间的所有右端点,即为Cnt,同时如果一个区间与其他区间没有交集,那这个区间必须取一个点,所以Ans >= Cnt。
const int N = 100010;
int n;
struct Range
{
int l, r;
//这是重载了小于号
bool operator< (const Range& w) const
{
return r < w.r;
}
}range[N];
int main() {
//省略录入数据部分(其实之前也省了)
sort(range, range + n);
int res = 0; ed = -2e9;
for(int i = 0; i < n; i++)
if (range[i].l > ed)
{
res++;
ed = range[i].r;
}
printf("%d\n", res);
return 0;
}
最大不相交区间数量
做法和上一个问题差不多,也是右端点从小到大排序依次枚举每个区间,有就过,没有就选右端点。选择的端点数量就是区间数量
证明省略,代码和上一题完全一样。
区间分组
首先按照左端点从小到大排序,然后从前往后处理区间,判断能否放到某个现有的组中(判断一下当前组中最大的右端点是不是小于这个区间的左端点),如果能放就放并更新最大右端点,不能就开新组。
我们用cnt划分出来的组,它们的集合之间是有公共点的,所以一定会区分出cnt个组,可得Ans >= Cnt,进而可得贪心解即为最优解
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range& w) const
{
return l < w.l;
}
}range[N];
int main() {
//省略录入数据部分
sort(range, range + n);
priority_queue<int, vector<int>, greater<int>> heap;
for (int i = 0; i < n; i++)
{
auto r = range[i];
if (heap.empty() || heap.top() >= r.l) heap.push(r.r);
else
{
int t = heap.top();
heap.pop();
heap.push(r.r);
}
}
printf("%d\n", heap.size());
return 0;
}
区间覆盖
首先将所有区间按左端点从小到大排序,然后依次枚举每个区间,在所有能覆盖start的区间中选择右端点最大的区间,然后将start更新成右端点的最大值。
证明省略
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range& w) const
{
return l < w.l;
}
}range[N];
int main() {
//省略录入数据部分
int st, ed;
int res = 0;
bool result = false;
for (int i = 0; i < n; i++)
{
int j = i, r = -2e9;
while (j < n && range[j].l <= st)
{
r = max(r, range[j].r);
j++;
}
if (r < st) {
res = -1;
break;
}
res++;
if (r >= ed)
{
result = true;
break;
}
st = r;
i = j - 1;
}
if (!result) res = -1;
printf("%d\n", res);
return 0;
}
Huffman树
Huffman树就是叶子节点的和是父节点的值。
合并果子
就是每次都选择当前所有的堆中最小的两个堆合并,这个过程也是一个构建树的过程
构建好树以后,叶子节点就是最开始的n堆,有几层父节点就是需要算几次。总和加起来就是总代价
using namespace std;
int main() {
int n;
scanf("%d", &n);
priority_queue<int, vector<int>, greater<int>> heap;
while (n--)
{
int x;
scanf("%d", &x);
heap.push(x);
}
int res = 0;
while (heap.size() > 1)
{
int a = heap.top(); heap.pop();
int b = heap.top(); heap.pop();
res += a + b;
heap.push(a + b);
}
printf("%d", res);
return 0;
}
绝对值不等式
货仓选址
这个是在数轴上选点,数轴上有一些已经选好的点,要求新选择的点到其他点的距离之和最小
选择在中间的点就行,假如我们有a,b,c,d四个点在数轴上依次排开,我们只要选择bc中间的点即可。我们将问题分组来看,先看a,d两点选择的点在ab之间的话,距离最小且相等。而b,c点就在ab之间,所以再选bc之间的即满足bc又满足ab,以此类推。
const int N = 10010;
int n;
int a[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
sort(a, a + n);
int res = 0;
for (int i = 0; i < n; i++) res += abs(a[i] - a[n / 2]);
printf("%d", res);
return 0;
}