贪心经典例题

目录

区间问题

905. 区间选点

908. 最大不相交区间数量

906. 区间分组

907. 区间覆盖

Huffman树

148. 合并果子

排序不等式

913. 排队打水 

绝对值不等式

104. 货仓选址 

推公式

125. 耍杂技的牛 


区间问题

905. 区间选点

给定 N 个闭区间 [ a[i], b[i] ],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。

输出选择的点的最小数量。

位于区间端点上的点也算作区间内。

输入格式

第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 a[i], b[i],表示一个区间的两个端点。

输出格式

输出一个整数,表示所需的点的最小数量。

数据范围

1 ≤ N ≤ 10^5

−10^9 ≤ a[i] ≤ b[i ]≤10^9

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

思路

1 按照右端点升序排序;

2 枚举右端点,判断是否满足区间中已经包含点的条件

        若该区间中已经包含点,则直接pass

        反之,将区间右端点作为该区间的点

证明可行性:

证明 1:贪心为短视,可以解释为选择当前的最优解。我们的目的是选择最小的点覆盖所有区间,当只看第一段区间时,可以分析得出,选择右端点的情况更可能覆盖更多的区间;故,每次按照区间的右端点来作为已经包含点。

证明2:我们得到的答案 cnt == ans 所有方案中的最小值

分两个方面:

        2.1 ans <= cnt,cnt 可以为所有可行方案,而 ans 为所有可行方案中的最小值,故成立

        2.2 ans >= cnt,我们根据定义可以得出有 cnt 个独立的区间,故至少需要 cnt 个点,故成立

所以 cnt == ans 得证

代码如下

#include <iostream>
#include <algorithm>

using namespace std;

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()
{
    scanf("%d",&n);
    for(int i = 0; i < n; i ++ )
    {
        int l ,r;
        scanf("%d%d",&l,&r);
        range[i] = {l, r};
    }
    
    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;
}

908. 最大不相交区间数量

给定 N 个闭区间 [ a[i],b[i] ],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。

输出可选取区间的最大数量。

输入格式

第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 a[i], b[i],表示一个区间的两个端点。

输出格式

输出一个整数,表示可选取区间的最大数量。

数据范围

1 ≤ N ≤ 10^5,
−10^9 ≤ a[i] ≤ b[i] ≤ 10^9

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

抽象模型与上一题一样,考虑方式略有不同;

上一题为在区间右端点放点,本题为选择相交区间中的任意一个区间,总而言之都是,再相交的区间中选择一个东西,再没相交的区间中选择一个东西

代码如下

#include <iostream>
#include <algorithm>

using namespace std;

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()
{
    scanf("%d",&n);
    for(int i = 0; i < n; i ++ )
    {
        int l ,r;
        scanf("%d%d",&l,&r);
        range[i] = {l, r};
    }
    
    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;
}

906. 区间分组

给定 N 个闭区间 [ a[i], b[i] ],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

输出最小组数。

输入格式

第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 a[i], b[i],表示一个区间的两个端点。

输出格式

输出一个整数,表示最小组数。

数据范围

1 ≤ N ≤ 10^5,
−10^9 ≤ a[i] ≤ b[i] ≤ 10^9

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

思路:

1 将所有区间按照左端点从小到大排序

2 从前往后处理每个区间

        判断是否可以放到当前的某个组中

                2.1 若与最小的组都有交集,则重新创建一个组

                2.2 若与最小的组无交集(实际上与每个组比较交集都一样,但因为与最小的组相比较最不容易产生交集,这点也是贪心的思路)则将该区间加入最小的组中,更新最小组的末尾数

代码如下

#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

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()
{
    scanf("%d",&n);
    
    for(int i = 0; i < n; i ++ )
    {
        int l, r;
        scanf("%d%d",&l,&r);
        range[i] = {l, r};
    }
    
    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
        {
            heap.pop();
            heap.push(r.r);
        }
    }
    
    printf("%d\n",heap.size());
    
    return 0;
}

907. 区间覆盖

给定 N 个闭区间 [ a[i] , b[i] ] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。

输出最少区间数,如果无法完全覆盖则输出 −1。

输入格式

第一行包含两个整数 s 和 t,表示给定线段区间的两个端点。

第二行包含整数 N,表示给定区间数。

接下来 N 行,每行包含两个整数 a[i] , b[i] ,表示一个区间的两个端点。

输出格式

输出一个整数,表示所需最少区间数。

如果无解,则输出 −1。

数据范围

1 ≤ N ≤ 10^5,
−10^9 ≤ a[i] ≤ b[i] ≤ 10^9,
−10^9 ≤ s ≤ t ≤ 10^9

输入样例:

1 5
3
-1 3
2 4
3 5

输出样例:

2

思路:

1 将所有区间按照左端点从小到大排序

2 从小到大依次枚举每个区间,在所有能覆盖 start 的区间中,选择右端点最大的区间,然后将     start 更新为该区间的右端点

代码如下:

#include <iostream>
#include <algorithm>

using namespace std;

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;
    scanf("%d %d",&st,&ed);
    
    scanf("%d",&n);
    
    for(int i = 0; i < n; i ++ )
    {
        int l ,r;
        scanf("%d%d",&l, &r);
        range[i] = {l ,r};
    }
    
    sort(range,range + n);
    
    int res = 0;
    bool success = 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)
        {
            success = true;
            break;
        }
        
        st = r;
        i = j - 1;
    }
    
    if(!success) res = -1;
    
    printf("%d\n",res);
    
    return 0;
}

Huffman树

148. 合并果子

在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。

达达决定把所有的果子合成一堆。

每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。

可以看出,所有的果子经过 n−1 次合并之后,就只剩下 1 堆了。

达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。

假定每个果子重量都为 1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 种果子,数目依次为 1,2,9。

可以先将 1、2 堆合并,新堆数目为 3,耗费体力为 3。

接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12,耗费体力为 12。

所以达达总共耗费体力 = 3 + 12 = 15。

可以证明 15 为最小的体力耗费值。

输入格式

输入包括两行,第一行是一个整数 n,表示果子的种类数。

第二行包含 n 个整数,用空格分隔,第 i 个整数 ai 是第 i 种果子的数目。

输出格式

输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。

输入数据保证这个值小于 231。

数据范围

1 ≤ n ≤ 10000,
1 ≤ ai ≤ 20000

输入样例:

3 
1 2 9 

输出样例:

15

思路: 最终答案为 每一堆果子 * 本堆果子合并的次数的和,合并的次数是一定的

假设我们由 a b c d e 五堆果子,可以构造如图所示的Huffman树

 最终结果 = a*3 + b*3 + c*2 + d * 2 + e * 2,无论如何排布,只能调换a b c d e 的顺序,不能改变其累加的次数,如此,我们只需要将最小的值放到树的最底部,可以令结果最小

用一个小根堆来维护最小值

代码如下

#include <iostream>
#include <algorithm>
#include <queue>

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

排序不等式

913. 排队打水 

有 n 个人排队到 1 个水龙头处打水,第 i 个人装满水桶所需的时间是 ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?

输入格式

第一行包含整数 n。

第二行包含 n 个整数,其中第 i 个整数表示第 i 个人装满水桶所花费的时间 ti。

输出格式

输出一个整数,表示最小的等待时间之和。

数据范围

1 ≤ n ≤ 10^5,
1 ≤ t[i] ≤ 10^4

输入样例:

7
3 6 1 4 2 5 7

输出样例:

56

思路:仍然的累加的思想,第一个接水的人的时间被累加到后面的人的时间中,第二个人的接水时间累加到剩下的人的时间中,如此,我们只需要将最小的接水时间放在最前面,便可使总时长最小。

代码如下:

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
int t[N];

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

绝对值不等式

104. 货仓选址 

在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。

输入格式

第一行输入整数 N。

第二行 N 个整数 A1∼AN。

输出格式

输出一个整数,表示距离之和的最小值。

数据范围

1 ≤ N ≤ 100000,
0 ≤ Ai ≤ 40000

输入样例:

4
6 2 9 1

输出样例:

12

思路:有绝对值不等式可知,两点之前的部分举例两点的举例和最短,如此我们只需找到所有点的中间值即可,

将数据分组分析,a[1] 与 a[n] 一组,a[2] 与 a[n-1] 一组,如此最中间的一组为 a[n/2] 与 a[n/2 - 1] 之间,我们只需将 x 取到任意的最中间的值,即可保证结果最小

代码如下

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 100010;

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

推公式

125. 耍杂技的牛 

农民约翰的 N 头奶牛(编号为 1..N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。

奶牛们不是非常有创意,只提出了一个杂技表演:

叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。

奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。

这 N 头奶牛中的每一头都有着自己的重量 W[i] 以及自己的强壮程度 S[i]。

一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。

您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。

输入格式

第一行输入整数 N,表示奶牛数量。

接下来 N 行,每行输入两个整数,表示牛的重量和强壮程度,第 i 行表示第 i 头牛的重量 W[i] 以及它的强壮程度 S[i] 。

输出格式

输出一个整数,表示最大风险值的最小可能值。

数据范围

1 ≤ N ≤ 50000,
1 ≤ W[i] ≤ 10,000,
1 ≤ S[i] ≤ 1,000,000,000

输入样例:

3
10 3
2 5
3 3

输出样例:

2

思路

每头牛的风险值为:上方的牛的总重量 - 本头牛的强壮程度,

先按照默认顺序,然后,我们尝试交换两头牛,看能否使风险值减小,如图

 同时减去w[1] + ……+ w[i-1]

 我们要比较的为交换前的最大值,与交换后的最大值,如果交换后的风险系数的最大值小于交换前的风险系数的最大值,就可以交换,下面我们来推其中的规律

因为我们要比较最大值,而 w[i+1] - s[i] 大于 -s[i] 所以 -s[i] 我们可以暂且不考虑,而w[i] - s[i+1] 大于 -s[i+1] ,所以 - s[i+1] 我们暂且也不考虑,所以我们只比较 w[i] - s[i+1] 与 w[i + 1] - s[i] 的大小即可,

若: w[i] - s[i+1] >= w[i + 1] - s[i],证明换过牛之后确实减小风险值了,即、交换过的结果比当前结果更优,化简得 w[i] + s[i] >= w[i + 1] + s[i  + 1] ,即、我们将w[i] + s[i] 较大的牛安排在下面为较优的方案,如此排序,求出结果即可

代码如下

#include <iostream>
#include <algorithm>

using namespace std;

typedef pair<int,int> PII;

const int N = 50010;

int n;
int w[N],s[N];
PII cow[N];

int main()
{
    scanf("%d",&n);

    for(int i = 0; i < n; i ++ ) 
    {
        int w, s;
        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 w = cow[i].second, s = cow[i].first - w;
        res = max(res, sum - s);
    }
    
        sum += w;
    printf("%d\n",res);
    
    return 0;
}

  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AC自动寄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值