《算法竞赛从入门到国奖》算法基础:入门篇-贪心算法(上)

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》


🌸Yupureki🌸的简介:


目录

前言

简单贪心

[1. 货仓选址](#1. 货仓选址)

算法原理

实操代码

贪心证明

[2. 最大子段和](#2. 最大子段和)

算法原理

实操代码

[3. 纪念品分组](#3. 纪念品分组)

算法原理

实操代码

[4. 排座椅](#4. 排座椅)

算法原理

实操代码

[5. 矩阵消除](#5. 矩阵消除)

算法原理

实操代码


前言

贪心算法是两级分化很严重的算法,简单的题目会觉得理所当然,难的题目怎么想也想不出来。与其说是算法,贪心更多的是一种解题的思想,这种思想得具体问题具体分析,因此千变万化没有模板,我们可能上一个贪心题目会写,下一个就不会写了,并且两个题目之间可能没有明显的共同点。有时候,自己想出来的贪心算法是错误的,因此我们需要证明是否正确,但证明的过程一般比较复杂,在时间充裕的时候可以尝试证明。

简单贪心

1. 货仓选址

题目链接:

P10452 货仓选址 - 洛谷

算法原理

贪心思想:将商店的位置由从小到大排序,然后把货仓建在商店位置的中位数。(证明在后面)

因此如果n是奇数,货舱建在v[(n+1)/2]位置处

如果n是偶数,货舱建在v[n/2]到v[n/2+1]之间均可

实操代码

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
    int n;cin>>n;
    vector<int> v;
    while(n--)
    {
        int num;cin>>num;
        v.push_back(num);
    }
    sort(v.begin(),v.end());
    long long ret = 0;
    for(int i = 0;i<v.size()/2 + v.size()%2;i++)
    {
        ret += v[v.size() - i - 1] - v[i];
    }
    cout<<ret;
    return 0;
}

贪心证明

我们假设对于在这个数轴上,有8个商店

我们先看1和15

只要我们把货仓建在1和15的中间,这个货舱距离1的距离和15的距离之和一定相等

既然1和15的距离和得到了,我们再看3和13的距离和,只要货仓在3和13的中间,距离和就一定是定值10

我们就这样不停的取最左边和最右边的两个数,只要保证货仓在这两个数字的中间,那么距离和就一定为定值,直到7和10,最后的货仓地址选在7和10中间,就能保证左右两个商点的距离和等于地址之差的绝对值。

那我不选左右两个值的中间怎么样?很简单,肯定会大于原来的定值的

如果商店个数是奇数的话,直接选择最中间的商店位置即可

之后的贪心算法我们不再证明,因为比较花时间和费脑子。我们在算法竞赛中是不可能有时间去证明你的贪心算法的正确性的,因此我们对于一道贪心题目,得大胆地选择一个方法,就是赌自己想得没有问题,如果赌错了,只能静下心来重新思考了

2. 最大子段和

题目链接:

P1115 最大子段和 - 洛谷

算法原理

贪心算法:

我们从头遍历数组,保证:当对于每个right,使得[left,right]中的和是以right为右边界的所有子段和的最大值

那么这个[left,right]的子段和我们可以拆分为[left,right-1] + v[right]

我们right是从right-1加1变过来的,因此对于之前的right-1为右边界的子段和中,[left,right-1]已经是最大的了

如果[left,right-1]为负值,那么就是给right帮倒忙,之前区间的最大值是个负值,那我还要干什么,因此此时的最大字段和就是v[right],随后更新left = right

如果[left,right - 1]为正值,那么最大值是[left,right]

在具体代码中,我们将prev记作[left,right - 1]的字段和最大值,一直维护更新即可

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int n;cin >> n;
    vector<long long> v(n+1);
    for(int i = 1;i<=n;i++)
    {
        long long a;cin>>a;
        v[i] = a;
    }
    int right = 1;
    long long ret = -2e9;
    long long prev = 0;
    while(right <= n)
    {
        if(prev < 0)
        {
            ret = max(ret,v[right]);
            prev = v[right];
        }
        else
        {
            ret = max(ret,prev + v[right]);
            prev += v[right];
        }
        right++;
    }
    cout<<ret;
    return 0;
}

3. 纪念品分组

题目链接:

P1094 [NOIP 2007 普及组] 纪念品分组 - 洛谷

算法原理

贪心算法:

将所有的价格从小到大排序,我们每组先从剩余的价格中选择最大的那个,然后从价格最小的选,一直选,直到拉满要超过上限为止

例如对于样例,排完序后为

20 20 30 50 60 70 80 90 90

第一组:先选90,然后从最左边的20开始选,发现超过了上限,就不要了

剩余: 20 20 30 50 60 70 80 90

第二组:先选90,然后从最左边的20开始选,发现超过了上限,就不要了

剩余: 20 20 30 50 60 70 80

第三组:先选80,然后从最左边的20开始选,拿一个20,再拿一个20发现超过了上限,就不要了

剩余:20 30 50 60 70

第四组:先选70,然后从最左边的20开始选,拿一个20,再拿一个30发现超过了上限,就不要了

剩余:30 50 60

第五组:先选60,然后从最左边的30开始选,拿一个30,再拿一个50发现超过了上限,就不要了

剩余: 50

第六组:直接拿50

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    int n, m; cin >> n >> m;
    vector<int> v;
    while (m--)
    {
        int num; cin >> num;
        v.push_back(num);
    }
    sort(v.begin(), v.end());
    int ret = 0;
    int left = 0;
    int sum = v[left];
    int right = v.size() - 1;
    while (left <= right)
    {
        int sum = v[left];
        if (sum + v[right] <= n)
            left++;
        ret++;
        right--;
    }
    cout << ret;
}

4. 排座椅

题目链接:

P1056 [NOIP 2008 普及组] 排座椅 - 洛谷

算法原理

贪心算法:

由题意可知,设置横向通道不会影响左右相邻的人,设置纵向通道不会影响上下相邻的人

因此我们横向和纵向分别处理

设置横向通道:收集每一条横向通道交头接耳的人的个数,从小到大排序,选择最大的前K个

处理纵向通道同理

实操代码

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;

struct node
{
int index;
int cnt;
}row[N], col[N];

int m, n, k, l, d;
// 按照 cnt 从⼤到⼩排序
bool cmp1(node& x, node& y)
{
    return x.cnt > y.cnt;
}
// 按照 index 从⼩到⼤排序
bool cmp2(node& x, node& y)
{
    return x.index < y.index;
}

int main()
{
    cin >> m >> n >> k >> l >> d;
    // 初始化结构体数组
    for(int i = 1; i <= m; i++) row[i].index = i;
    for(int i = 1; i <= n; i++) col[i].index = i;
    while(d--)
    {
        int x, y, p, q; cin >> x >> y >> p >> q;
        if(x == p) col[min(y, q)].cnt++;
        else row[min(x, p)].cnt++;
    }
    // 对两个数组按照 cnt 从⼤到⼩排序
    sort(row + 1, row + 1 + m, cmp1);
    sort(col + 1, col + 1 + n, cmp1);
    // 对 row 数组,前 k 个元素,按照下标从⼩到⼤排序
    sort(row + 1, row + 1 + k, cmp2);
    // 对 col 数组,前 l 个元素,按照下标从⼩到⼤排序
    sort(col + 1, col + 1 + l, cmp2);
    for(int i = 1; i <= k; i++)
    {
        cout << row[i].index << " ";
    }
    cout << endl;
    for(int i = 1; i <= l; i++)
    {
        cout << col[i].index << " ";
    }
    cout << endl;
    return 0;
}

5. 矩阵消除

题目链接:

矩阵消除游戏

算法原理

错误的贪心:每次选择最大的一行或者一列

例如以下矩阵

100 9 1

0 0 0

100 0 2

我们会选择第一列和第二列,然而最佳的选择方法是选择第一行和第二行

正确的贪心:

由于n和m比较小,因此我们可以无脑枚举

我们先枚举选择列的情况,随后更新数组中的数据,然后在每一行的情况中从大到小选择

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    int n, m, k; cin >> n >> m >> k;
    k = min(k, n + m);
    vector<vector<int>> v(n, vector < int > (m));
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            int num; cin >> num;
            v[i][j] = num;
        }
    }
    int ret = 0;
    for (int i = 0; i < (1 << n); i++)
    {
        int tmp1 = i;
        int count = 0;
        vector<int> bit;
        for (int j = 0; j < n; j++)
        {
            if ((tmp1 & (1 << j)) != 0)
                bit.push_back(j);
        }
        if (bit.size() > k)
            continue;
        auto tmp = v;
        int sum = 0;
        for (int sz = 0; sz < bit.size(); sz++)
        {
            for (int j = 0; j < m; j++)
            {
                sum += tmp[bit[sz]][j];
                tmp[bit[sz]][j] = 0;
            }
        }
        if (bit.size() < k)
        {
            vector<int> y(16, 0);
            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n; j++)
                    y[i] += tmp[j][i];
            }
            sort(y.begin(), y.end());
            auto it = y.rbegin();
            for (int i = 0; i < k - bit.size(); i++)
            {
                sum += *it;
                it++;
            }
        }
        if (sum > ret)
            ret = sum;
    }
    cout << ret;
    return 0;
}
相关推荐
散峰而望2 小时前
【算法竞赛】队列和 queue
开发语言·数据结构·c++·算法·链表·github·线性回归
yuanmenghao2 小时前
车载Linux 系统问题定位方法论与实战系列 - 开篇: 为什么需要一套“系统化”的 Linux 问题定位方法
linux·运维·服务器·数据结构·c++·自动驾驶
丶小鱼丶2 小时前
Java基础之【排序算法】
java·算法
一只小bit2 小时前
Qt 对话框全方面详解,包含示例与解析
前端·c++·qt·cpp·页面
柏木乃一2 小时前
基础IO(上)
linux·服务器·c语言·c++·shell
乐迪信息2 小时前
乐迪信息:AI视频分析技术用于船舶倾斜监控
大数据·网络·人工智能·算法·无人机
知乎的哥廷根数学学派2 小时前
基于物理约束指数退化与Hertz接触理论的滚动轴承智能退化趋势分析(Pytorch)
开发语言·人工智能·pytorch·python·深度学习·算法·机器学习
CodeByV2 小时前
【算法题】字符串
数据结构·算法
Zilliz Planet2 小时前
官宣,Milvus开源语义高亮模型:告别饱和检索,帮RAG、agent剪枝80%上下文
人工智能·算法·机器学习·剪枝·milvus