算法——贪心算法

1 区间选点

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

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

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

输入格式

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

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

输出格式

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

数据范围

1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1N105

− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10^9 \le a_i \le b_i \le 10^9 109aibi109

输入样例

3
-1 1
2 4
3 5

输出样例

2

y总的思路:

对所有的区间按照右端点从小到大排序

遍历这些区间,如果当前区间已经包含这个点(右端点),则直接pass;否则选择当前区间的右端点

自己的想法:

对所有的区间按照左端点排序

遍历区间,维护一个 [ l e f t , r i g h t ] [left, right] [left,right] 区间表示重叠部分,最后的结果就是有多少个这样的区间。

#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()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int l, r;
        cin >> 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)                   //只有新的区间的左端点严格大于 ed,才会更新ed
        {
            res ++;
            ed = range[i].r;
        }
    }
    
    cout << res << endl;
    
    return 0;
}

2 最大不相交区间数量

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

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

输入格式

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

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

输出格式

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

数据范围

1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1N105

− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10^9 \le a_i \le b_i \le 10^9 109aibi109

输入样例

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()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int l, r;
        cin >> 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;
        }
    }
    
    cout << res << endl;
    
    return 0;
}

3 区间分组

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

输出最小组数。

输入格式

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

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

输出格式

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

数据范围

1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1N105

− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10^9 \le a_i \le b_i \le 10^9 109aibi109

输入样例

3
-1 1
2 4
3 5

输出样例

2

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

2、从前往后处理每个区间

​ 判断能否将其放到某个现有的组中 range[i].l > Max_r

​ 1 如果不存在(后面的区间全遍历)这样的区间,开一个新的组

​ 2 如果存在这样的区间,将其放进去,并更新当前组的Max_r

动态维护最小值,用堆来维护

#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()
{
 	cin >> n;
    for (int i = 0; i < n; i++)
    {
        int l, r;
        cin >> 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 t = range[i];
        if (heap.empty() || heap.top() >= t.l) heap.push(t.r);    //如果heap中的最小的组的右端点,比当前的l还要大,需要另开新组
        else 
        {
            heap.pop();              //相当于将这个区间添加到右端点值最小的那个组中
            heap.push(t.r);
        }
    }
    
    cout << heap.size() << endl;
    
    return 0;
}

4 区间覆盖

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

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

输入格式

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

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

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

输出格式

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

如果无解,则输出-1。

数据范围

1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1N105

− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 -10^9 \le a_i \le b_i \le 10^9 109aibi109

− 1 0 9 ≤ s ≤ t ≤ 1 0 9 -10^9 \le s \le t \le 10^9 109st109

输入样例

1 5
3
-1 3
2 4
3 5

输出样例

2

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

2、从前往后依次枚举每个区间,在所有能覆盖开始(start)端点的区间中,选择右端点值最大的区间 ,选完之后,将start更新成右端点的最大值

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

using namespace std;

const int N = 100010;

int n, st, ed;
struct Range
{
    int l, r;
    bool operator< (const Range& w) const
    {
        return l < w.l;
    }
}range[N];

int main()
{
    cin >> st >> ed;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int l, r;
        cin >> l >> r;
        range[i] = {l, r};
    } 
    
    sort(range, range + n);
    
    int res = 0, i = 0;
    bool success = false;
    while(i < n)
    {
        int j = i, r = -2e9;                   //双指针算法,遍历所有左端点在start左边,右端点最大的值为多少
        while (j < n && range[j].l <= st)
        {
            r = max(r, range[j].r);
            j ++;
        }
        if (r < st)                           //最大的右端点都小于st,说明无解
        {
            res = -1;
            break;
        }
        
        res ++;
        if (r >= ed)
        {
            success = true;                  //只有这里是成功的
            break;
        }
        
        st = r;                      //更新st端点
        i = j;
    }
    
    if (!success) res = -1;
    cout << res << endl;
    
    return 0;
}

5 合并果子

哈夫曼树的典型应用,之前的动态规划也可以做

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

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

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

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

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

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

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

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

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

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

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

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

输入格式

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

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

输出格式

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

输入数据保证这个值小于 2 31 2^{31} 231

输入样例

3
1 2 9

输出样例

15

贪心的方法:每次从所有节点中选择值最小的两个节点,将它们合并并插入到剩下的节点中,然后接着寻找最小的两个节点。

最后得到的结果就是合并耗费体力值最小的。

Haffman树,关于上面的方法的正确性,建议自己看acwing视频(^ 0 ^)

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

using namespace std;

int main()
{
    int n;
    cin >> n;
    
    priority_queue<int, vector<int>, greater<int>> heap;
    while (n--)
    {
        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;
}

priority_queue对于自定义的数据结构进行排序

#include <queue>
using namespace std;

struct node
{
    int x, y;
}arr[200];

struct cmp1
{
  bool operator () (const node &a, const node &b) const
  {
      return a.x < b.x;                    //这样优先队列排出来的是 从大到小
  }
};

int main()
{
    priority_queue<node, vector<node>, cmp1> heap1;
    
    return 0;
}

6 排队打水

排序不等式问题。

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

输入格式

第一行包含整数 n n n

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

输出格式

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

数据范围

1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105

1 ≤ t i ≤ 1 0 4 1 \le t_i \le 10^4 1ti104

输入样例

7
3 6 1 4 2 5 7

输出样例

56

将花费时间从小到大排序。

假设安排了第 i i i 个人在第一个打水,那么后面的人需要等待他 n - 1打水的时间,显然这个打水的时间越小,最后的值也越小。

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;             //用 long long ,有可能爆int

const int N = 100010;

int t[N];
int n;

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> t[i];
    }
    
    sort(t, t + n);
    
    LL res = 0;
    for (int i = 0; i < n - 1; i++)
    {
        res += t[i] * (n - i - 1);          
    }
    
    cout << res << endl;
}

7 货仓选址

绝对值不等式问题。

在一条数轴上有 N N N 家商店,它们的坐标分别为 A 1 A_1 A1 ~ A N A_N AN

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

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

输入格式

第一行输入整数 N N N

第二行 N N N 个整数 A 1 A_1 A1 ~ A N A_N AN

输出格式

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

数据范围

1 ≤ N ≤ 100000 1 \le N \le 100000 1N100000
0 ≤ A i ≤ 40000 0 \le A_i \le 40000 0Ai40000

输入样例

4
6 2 9 1

输出样例

12

排序之后,将仓库安排在最中间就好了。

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;
int a[N];
int n;

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    
    sort(a, a + n);
    
    int res = 0, mid = n >> 1;
    for (int i = 0; i < mid; i++) 
        res += a[n - i - 1] - a[i];
    
    cout << res << endl;
}

8 耍杂技的牛

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

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

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

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

N N N 头奶牛中的每一头都有着自己的重量 W i W_i Wi 以及自己的强壮程度 S i S_i Si

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

您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。(比较 N N N 个风险系数)

输入格式

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

接下来 N N N 行,每行输入两个整数,表示牛的重量和强度,第 i i i 行表示第 i i i 头牛的重量 W i W_i Wi 和强度 S i S_i Si

输出格式

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

数据范围

1 ≤ N ≤ 50000 1 \le N \le 50000 1N50000

1 ≤ W i ≤ 10 , 000 1 \le W_i \le 10,000 1Wi10,000

1 ≤ S i ≤ 1 , 000 , 000 , 000 1 \le S_i \le 1,000,000,000 1Si1,000,000,000

输入样例

3
10 3
2 5
3 3

输出样例

2

按照 W i + S i W_i + S_i Wi+Si 从小到大的顺序去排,最大的危险系数一定是最小的。

证明这样的做法是正确的。我们用反证法来证明,

贪心得到的结果 >= 最优值

贪心得到的结果 <= 最优值

第一种,显然是成立的,最优值一定最小

第二种情况,贪心的做法,si + wi排序,若最优值不是采用我们的做法,则一定存在 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个位置的牛
交换前 w 1 + w 2 + . . . + w i − 1 − s i w_1 + w_2 + ... + w_{i-1} - s_i w1+w2+...+wi1si w 1 + w 2 + . . . + w i − s i + 1 w_1 + w_2 + ... + w_i - s_{i+1} w1+w2+...+wisi+1
交换后 w 1 + w 2 + . . . + w i − 1 − s i + 1 w_1 + w_2 + ... + w_{i-1} - s_{i+1} w1+w2+...+wi1si+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 ,然后全部加上 s i + s i + 1 s_i + s_{i + 1} si+si+1

第 i 个位置的牛第 i + 1个位置的牛
交换前 s i + 1 s_{i+1} si+1 w i + s i w_i + s_i wi+si
交换后 s i s_i si 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 > s i  and  s i + 1 w_i + s_i > w_{i+1} + s_{i+1} > s_i \ \text{and} \ s_{i+1} wi+si>wi+1+si+1>si and si+1

因而,贪心得到的结果 <= 最优值

#include <iostream>
#include <algorithm>

using namespace std;

typedef pair<int, int> PII;

const int N = 50010;

int n;
PII cow[N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int w, s;
        cin >> w >> s;
        cow[i] = {w + s, w};             //这样存储省去了自定义排序,值得学习
    }
    
    sort(cow, cow + n);
    
    int res = -2e9, sum = 0;
    
    for (int i = 0; i < n; i++)
    {
        res = max(res, sum - cow[i].first + cow[i].second);
        sum += cow[i].second;
    }
    
    cout << res << endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值