毕业!第六章 贪心(一、二)——区间问题,Huffman树,不等式与推公式

6.18~7.22完成算法基础的学习,剩下时间用来暴刷《算法竞赛指南》以巩固基础

区间问题

905. 区间选点

905. 区间选点 - AcWing题库
image.png

选择尽可能少的点,使得每一个区间都包含一个点

将所有区间按照右端点排序,每次选择右端点,这样当前区间就包含了一个点
由于之后的区间右端点都是大于等于当前区间右端点的,所以只要后续区间与当前区间相交,那么当前区间的右端点也被后续区间包含
若后续区间与当前区间互不相交,那么选择该区间的右端点

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

const int N = 1e5 + 10;
struct range
{
    int l ,r;
    bool operator<(const range& x)
    {
        return r < x.r;
    }
}ranges[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i) scanf("%d%d", &ranges[i].l, &ranges[i].r);
    sort(ranges, ranges + n);
    
    int res = 0, r = -2e9;
    for (int i = 0; i < n; ++ i ) 
    {
        if (ranges[i].l > r)
        {
            r = ranges[i].r;
            res ++ ;
        }
    }
    
    printf("%d\n", res);
    return 0;
}

908. 最大不相交区间数量

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

求出给定区间中,最多的互不相交区间数量

将所有区间按照右端点升序排序,遍历所有区间
记互不相交的区间中右端点的最大值为mr

  1. 若当前区间的左端点大于mr,那么当前区间与所有互不相交的区间互不相交,将其加入互不相交区间,更新mr
  2. 若当前区间的左端点小于mr,那么当前区间与互不相交的区间中某个区间相交

为什么该贪心策略能选择最多的互不相交区间?所有的区间按照右端点排序,每次选择一个区间,使之与已经选择的互不相交区间不相交。同时该区间的右端点最小,说明之后能选择的区间数量最多

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

const int N = 1e5 + 10;
struct range
{
    int l, r;
    bool operator<(const range& x)
    {
        return r < x.r;
    }
}ranges[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i ) scanf("%d%d", &ranges[i].l, &ranges[i].r);
    sort(ranges, ranges + n);
    
    int mr = -2e9, res = 0;
    for (int i = 0; i < n; ++ i ) 
    {
        if (ranges[i].l > mr)
        {
            res ++ ;
            mr = ranges[i].r;
        }
    }
    printf("%d\n", res);
    return 0;
}

906. 区间分组

906. 区间分组 - AcWing题库
image.png

将所有的区间分组,使得每一组的区间互不相交,并且分的组尽可能少

将所有区间按左端点排序,用小堆维护所有组的右端点最大值,使得堆顶是这些最大值的最小值

  1. 若当前区间的左端点大于堆顶,说明当前区间可以加入所有组中,右端点最大值最小的一组,加入后用当前区间的右端点更新该组右端点最大值
  2. 若当前区间的左端点小于等于堆顶,说明所有组中都存在一个区间 [ l , r ] [l, r] [l,r],l小于等于左端点并且r大于等于左端点。即区间重叠产生了冲突,当前区间无法加入任意一组区间,只能将当前区间放入一个新的组
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;

const int N = 1e5 + 10;
struct range
{
    int l, r;
    bool operator<(const range& x)
    {
        return l < x.l;
    }
}ranges[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i ) scanf("%d%d", &ranges[i].l, &ranges[i].r);
    sort(ranges, ranges + n);
    priority_queue<int, vector<int>, greater<int>> q;
    
    
    for (int i = 0; i < n; ++ i )
    {
        if (q.empty() || ranges[i].l <= q.top()) // 开新组
            q.push(ranges[i].r);
        else 
        {
            q.pop();
            q.push(ranges[i].r);
        }
    }
    printf("%d\n", q.size());
    
    return 0;
}

907. 区间覆盖

907. 区间覆盖 - AcWing题库
image.png

给定区间 [ s , t ] [s, t] [s,t],用最少的区间覆盖给定区间
将所有区间按照左端点排序

  1. 对于所有左端点小于等于s的区间,选择右端点最大的区间
  2. 用该区间的右端点更新s,即 s = r s=r s=r

重复以上两个步骤,直到 s > = t s >= t s>=t,此时 [ s , t ] [s, t] [s,t]区间被完全覆盖,所以重复次数就是需要的最少区间数量
其中有两个无解的情况:

  1. 对于所有左端点小于等于s的区间,最大的右端点小于等于s
  2. 所有的区间更新完, s < t s < t s<t

若遇到第一种情况直接退出更新,否则可能陷入死循环

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

const int N = 1e5 + 10;
struct range
{
    int l, r;
    bool operator<(const range& x)
    {
        return l < x.l;
    }
}ranges[N];

int main()
{
    int s, t, n;
    scanf("%d%d%d", &s, &t, &n);
    for (int i = 0; i < n; ++ i ) scanf("%d%d", &ranges[i].l, &ranges[i].r);
    sort(ranges, ranges + n);
    
    int i = 0, r = -2e9, res = 0;
    bool flag = false;
    while (i < n)
    {
        while (i < n && ranges[i].l <= s)
        {
            r = max(r, ranges[i].r);
            i ++ ;
        }
        if (r <= s && s != t) break;
        s = r;
        res ++ ;
        if (s >= t) 
        {
            flag = true;
            break;
        }
    }
    
    if (flag) printf("%d\n", res);
    else puts("-1");
    
    return 0;
}

debug:给定区间的s和t可能相等,此时右端点最大值被更新成s时不能直接break,所以这里要特判一下:if (r <= s && s != t) break;


Huffman树

148. 合并果子

148. 合并果子 - AcWing题库
image.png

经典Huffman问题,节点权值与根节点的距离具有负相关性,权值越大的节点离根节点的距离越近,权值越小的节点离根节点的距离越远,此时合并果子消耗的体力最小
所有叶子节点乘以其到根节点的距离累加,为消耗的总体力值

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

priority_queue<int, vector<int>, greater<int>> q;

int main()
{
    int n, x;
    scanf("%d", &n);
    
    for (int i = 0; i < n; ++ i ) 
    {
        scanf("%d", &x);
        q.push(x);
    }
    
    int res = 0;
    while (q.size() > 1)
    {
        int a = q.top(); q.pop();
        int b = q.top(); q.pop();
        res += a + b;
        q.push(a + b);
    }
    
    printf("%d\n", res);
    return 0;
}

排序不等式

913. 排队打水

913. 排队打水 - AcWing题库
image.png

打水时间短的人排在前面,长的人排在后面,这样总时间最小
因为第i个人打水,后面n-i个人都要等待他打水结束,有等式:
t 1 ( n − 1 ) + t 2 ∗ ( n − 2 ) + . . . + t i ( n − ( n − 1 ) ) t_1(n-1)+t_2*(n-2)+...+t_i(n-(n-1)) t1(n1)+t2(n2)+...+ti(n(n1))
前面的数乘以的数较大,后续的数乘以的数较小,t乘以的数从大到小,要使得结果最小,就要使t从小到大

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

const int N = 1e5 + 10;
int a[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i ) scanf("%d", &a[i]);
    sort(a, a + n);
    long long res = 0;
    for (int i = 0; i < n - 1; ++ i )
        res += a[i] * (n - 1 - i);
    
    printf("%lld\n", res);
    
    return 0;
}

绝对值不等式

104. 货仓选址

104. 货仓选址 - AcWing题库
image.png

绝对值不等式:
∣ x − a ∣ + ∣ x − b ∣ > = ∣ a − b ∣ |x-a|+|x-b|>=|a-b| xa+xb>=ab
在x建立一个货仓,使得它离其他商店最近,需要使式子最小:
∣ x 1 − x ∣ + ∣ x 2 − x ∣ + . . . + ∣ x n − 1 − x ∣ + ∣ x n − x ∣ |x_1-x|+|x_2-x|+...+|x_{n-1}-x|+|x_n-x| x1x+x2x+...+xn1x+xnx
将首尾两个式子看成一组:
( ∣ x 1 − x ∣ + ∣ x n − x ∣ ) + . . . + ( ∣ x 2 − x ∣ + ∣ x n − 1 − x ∣ )   ( 1 ) (|x_1-x|+|x_n-x|)+...+(|x_2-x|+|x_{n-1}-x|)\ (1) (x1x+xnx)+...+(x2x+xn1x) (1)
由绝对值不等式有:
∣ x n − x 1 ∣ + . . . + ∣ x n − 1 − x 2 ∣   ( 2 ) |x_n-x_1|+...+|x_{n-1}-x_2|\ (2) xnx1+...+xn1x2 (2)
(2)式为(1)式的最小值

image.png
要让等号成立,就要让x在两数之间
若n为奇数,那么x等于中位数。若n为偶数,那么x在中间两个数(包含两个数)之间就行

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

const int N = 1e5 + 10;
int a[N];

int main()
{
    int n;
    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\n", res);
    return 0;
}

推公式

125. 耍杂技的牛

125. 耍杂技的牛 - AcWing题库
image.png

( w i + s i ) (w_i + s_i) (wi+si)为关键字进行升序排序,以从小到大的顺序从上到下摆放奶牛,此时的最大危险系数一定是所有摆放中最小的

证明:假设存在若 ( w i + s i ) > ( w i + 1 + s i + 1 ) (w_i + s_i) > (w_{i+1} + s_{i+1}) (wi+si)>(wi+1+si+1),风险系数是所有摆放中最小的
我们可以计算交换两头奶牛前后,第i个与第i+1个位置上的危险系数
交换前:
第i个位置的危险系数: ( w 1 + w 2 + . . . + w i − 1 − s i ) (w_1+w_2+...+w_{i-1}-s_i) (w1+w2+...+wi1si)
第i+1个位置的危险系数: ( w 1 + w 2 + . . . + w i − s i + 1 ) (w_1+w_2+...+w_i-s_{i+1}) (w1+w2+...+wisi+1)
交换后:
第i个位置的危险系数: ( w 1 + w 2 + . . . + w i − 1 − s i + 1 ) (w_1+w_2+...+w_{i-1}-s_{i+1}) (w1+w2+...+wi1si+1)
第i+1个位置的危险系数: ( w 1 + w 2 + . . . + w i − 1 + w i + 1 − s i ) (w_1+w_2+...+w_{i-1}+w_{i+1}-s_i) (w1+w2+...+wi1+wi+1si)
减去这四个式子中相同项: ( w 1 + w 2 + . . . + w i − 1 ) (w_1+w_2+...+w_{i-1}) (w1+w2+...+wi1)

交换前:
第i个位置的危险系数: ( − s i ) (-s_i) (si)
第i+1个位置的危险系数: ( w i − s i + 1 ) (w_i-s_{i+1}) (wisi+1)
交换后:
第i个位置的危险系数: ( − s i + 1 ) (-s_{i+1}) (si+1)
第i+1个位置的危险系数: ( w i + 1 − s i ) (w_{i+1}-s_i) (wi+1si)
再加上: ( s i + s i + 1 ) (s_i+s_{i+1}) (si+si+1)

交换前:
第i个位置的危险系数: ( s i + 1 ) (s_{i+1}) (si+1)
第i+1个位置的危险系数: ( w i + s i )   ( 2 ) (w_i+s_{i})\ (2) (wi+si) (2)
交换后:
第i个位置的危险系数: ( s i )   ( 3 ) (s_{i})\ (3) (si) (3)
第i+1个位置的危险系数: ( w i + 1 + s i + 1 ) (w_{i+1}+s_{i+1}) (wi+1+si+1)

已知: ( w i + s i ) > ( w i + 1 + s i + 1 ) (w_i + s_i) > (w_{i+1} + s_{i+1}) (wi+si)>(wi+1+si+1),观察(2)式与(3)式,我们能得到 ( w i + s i ) > m a x ( w i + 1 + s i + 1 , s i ) (w_i + s_i) > max(w_{i+1}+s_{i+1}, s_i) (wi+si)>max(wi+1+si+1,si)
同时 ( w i + s i ) > w i + 1 + s i + 1 > s i + 1 (w_i + s_i) > w_{i+1}+s_{i+1} > s_{i+1} (wi+si)>wi+1+si+1>si+1,所以交换前第i个位置与第i+1个位置的较大值为 ( w i + s i ) (w_i + s_i) (wi+si)
但是交换后第i个位置与第i+1个位置的较大值小于 ( w i + s i ) (w_i + s_i) (wi+si),也就是交换前第i个位置与第i+1个位置的较大值
即,交换过后危险系数变小了,与前提矛盾,所以摆放奶牛的序列中,不能存在 ( w i + s i ) > ( w i + 1 + s i + 1 ) (w_i + s_i) > (w_{i+1} + s_{i+1}) (wi+si)>(wi+1+si+1),即所有奶牛的 ( w + s ) (w+s) (w+s)需要从小到大地从上往下摆放,才能得到所有摆放中最小的最大危险系数

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

const int N = 1e5 + 10;
struct cow
{
    int s, w;
    bool operator<(const cow& x)
    {
        return (s + w) < (x.s + x.w);
    }
}cows[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i ) scanf("%d%d", &cows[i].w, &cows[i].s);
    sort(cows, cows + n);
    
    int res = -2e9, sum = 0;
    for (int i = 0; i < n; ++ i ) 
    {
        res = max(res, sum - cows[i].s);
        sum += cows[i].w;
    }
    printf("%d\n", res);
    return 0;
}

debug:res要设置为-2e9,因为第一头牛的危险系数可能最小且为负数,这时res就要设置成一个比它还小的数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值