NO.64十六届蓝桥杯备战|基础算法-简单贪心|货仓选址|最大子段和|纪念品分组|排座椅|矩阵消除(C++)

news2025/4/9 4:19:21

贪⼼算法是两极分化很严重的算法。简单的问题会让你觉得理所应当,难⼀点的问题会让你怀疑⼈⽣

什么是贪⼼算法?

贪⼼算法,或者说是贪⼼策略:企图⽤局部最优找出全局最优。

  1. 把解决问题的过程分成若⼲步;
  2. 解决每⼀步时,都选择"当前看起来最优的"解法;
  3. "希望"得到全局的最优解。
贪⼼算法的特点
  1. 对于⼤多数题⽬,贪⼼策略的提出并不是很难,难的是证明它是正确的。因为贪⼼算法相较于暴⼒枚举,每⼀步并不是把所有情况都考虑进去,⽽是只考虑当前看起来最优的情况。但是,局部最优并不等于全局最优,所以我们必须要能严谨的证明我们的贪⼼策略是正确的。
    ⼀般证明策略有:反证法,数学归纳法,交换论证法等等。
  2. 当问题的场景不同时,贪⼼的策略也会不同。因此,贪⼼策略的提出是没有固定的套路和模板的。我们后⾯讲的题⽬虽然分类,但是⼤家会发现具体的策略还是相差很⼤。因此,不要妄想做⼏道贪⼼题⽬就能遇到⼀个会⼀个。有可能做完50道贪⼼题⽬之后,第51道还是没有任何思路。
如何学习贪⼼?

先有⼀个认知:做了⼏⼗道贪⼼的题⽬,遇到⼀个新的⼜没有思路,这时很正常的现象,把⼼态放平。

  1. 前期学习的时候,重点放在各种各样的策略上,把各种策略当成经验来吸收;
  2. 在平常学习的时候,我们尽可能的证明⼀下这个贪⼼策略是否正确,这样有利于培养我们严谨的思维。但是在⽐赛中,能想出来⼀个策略就已经不错了,如果再花费⼤量的时间去证明,有点得不偿失。这个时候,如果根据贪⼼策略想出来的若⼲个边界情况都能过的话,就可以尝试去写代码了。
P10452 货仓选址 - 洛谷

将所有的商店按照「从⼩到⼤」的顺序「排序」,把货仓建在中位数处,可以使得货仓到每家商店的距离之和最⼩:

  • 如果n是奇数,货仓建在a[(n + 1)/2] 位置处;
  • 如果n是偶数,货仓建在a[n/2] ∼ a[n/2 + 1]之间都是可以的
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5+10;

int n;
int a[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    sort(a+1, a+1+n);

    LL ret = 0;
    for (int i = 1; i <= n; i++)
    {
        //中间位置的下标是(1+n)/2
        ret += abs(a[i] - a[(1+n) / 2]);
    }
    cout << ret << endl;
    
    return 0;
}

∣ a − x ∣ + ∣ b − x ∣ ≥ ∣ a − b ∣ |a-x|+|b-x| \ge |a-b| ax+bxab
![[Pasted image 20250404104208.png]]

当x处于两者的中间位置时,左式取得最小值
形如:
s u m = ∑ i = 1 n ∣ a [ i ] − x ∣ = ∣ a [ 1 ] − x ∣ + ∣ a [ 2 ] − x ∣ + ⋯ + ∣ a [ n ] − x ∣ sum = \sum^{n}_{i=1}|a[i]-x|=|a[1]-x|+|a[2]-x|+\dots+|a[n]-x| sum=i=1na[i]x=a[1]x+a[2]x++a[n]x

  • 当x 取到n 个数的中位数时,和最⼩;
  • 最⼩和为:
    ( a [ n ] − a [ 1 ] ) + ( a [ n − 1 ] − a [ 2 ] ) + . . . + ( a [ n + 1 − n / 2 ] − a [ n / 2 ] ) (a[n] - a[1]) + (a[n - 1] - a[2]) + ... + (a[n + 1 - n/2] - a[n/2]) (a[n]a[1])+(a[n1]a[2])+...+(a[n+1n/2]a[n/2])
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5+10;

int n;
int a[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    sort(a+1, a+1+n);

    LL ret = 0;
    for (int i = 1; i <= n/2; i++)
    {
        ret += abs(a[i] - a[n+1-i]);      
    }
    
    cout << ret << endl;
    
    return 0;
}
P1115 最大子段和 - 洛谷

贪⼼想法:从前往后累加,我们会遇到下⾯两种情况:

  • ⽬前的累加和>=0:那么当前累加和还会对后续的累加和做出贡献,那我们就继续向后累加,然后更新结果;
  • ⽬前的累加和<0:对后续的累加和做不了⼀点贡献,直接⼤胆舍弃计算过的这⼀段,把累加和重置为0,然后继续向后累加。
    这样我们在扫描整个数组⼀遍之后,就能更新出最⼤⼦段和。
    ![[Pasted image 20250404143727.png]]

在累加的过程中算出⼀段区间和sum[a,b]<0 ,如果不舍弃这⼀段,那么[a,b]段之间就会存在⼀点,「以某个位置为起点」就会「更优」,分为下⾯两种情况

  1. 在ab段存在⼀个点c ,从这个位置开始,「越过b 」的累加和⽐从a 开始的累加和更优:
    ⽤「反证法」证明这种情况不存在。
    如果存在这⼀点,那么:sum[c, b] > sum[a, b] ,这样才能保证向后加的时候更优。
    但这是「不可能」的。如果sum[c, b] > sum[a, b] ,那么sum[a, c-1]<0,这与我们的贪⼼策略⽭盾。
    因为我们贪⼼策略向后加的时候,只要不⼩于0,就会⼀直加下去。如果[a, c-1]段⼩于0,就
    会在c点之前停⽌,不会累加到b。
    因此区间内不存在⼀点,在计算⼦数组和时,在「越过b 」的情况下,能⽐从a 开始更优。
  2. 在ab段存在⼀个点c ,从这个位置开始,「不越过b 」的累加和⽐从a 开始的累加和更优:
    也可以⽤「反证法」证明这种情况不存在。
    如果存在这⼀点,那么:sum[c, k] > sum[a, k]
    但这是不可能的。如果sum[c, k] > sum[a, k],那么sum[a, c - 1] < 0 ,这与我们的贪⼼策略⽭盾。
    因此区间内不存在⼀点,在计算⼦数组和时,在「不越过b 」的情况下,能⽐从a 开始更优。
    综上所述,我们可以⼤胆舍弃这⼀段,重新开始。
#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

typedef long long LL;

int n;
LL a[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    LL sum = 0, ret = -1e6;
    for (int i = 1; i <= n; i++)
    {
        sum += a[i];
        ret = max(ret, sum);
        if (sum < 0) sum = 0;
    }
    cout << ret << endl;
    
    return 0;
}
P1094 [NOIP 2007 普及组] 纪念品分组 - 洛谷

先将所有的纪念品排序,每次拿出当前的最⼩值x 与最⼤值y :

  • 如果x + y ≤ w :就把这两个放在⼀起;
  • 如果x + y > w:说明此时最⼤的和谁都凑不到⼀起,y单独分组,x继续留下在进⾏下⼀次判断。
    直到所有的物品都按照上述规则分配之后,得到的组数就是最优解

可以⽤「交换论证法」证明贪⼼解就是最优解:
对于区间[ai......aj],如果存在最优解,但是aiaj的分配⽅式与我们贪⼼解的分配⽅式不⼀样,那么就会有以下⼏种情况:

  1. a[i] + a[j] > w时:
  • 贪⼼解会把a[j]单独分组,a[i]留待下次考虑;
  • 最优解也必定会把a[j]单独分组,因为没有更⼩的值与a[j]组合。
    此时贪⼼解与最优解⼀致。
  1. a[i] + a[j] ≤ w时:
  • 贪⼼解会把两者组合分在⼀个组⾥⾯;
  • 最优解可能有以下⼏种情况:
    • a[j]单独⼀组:
      • 如果a[i]也单独⼀组的话,最优解还不如贪⼼解分的组少,⽭盾;
      • 如果a[i]和另⼀个a[k]⼀组的话,我们可以把a[k]a[j]交换,此时并不影响结果,和贪⼼解⼀致。
    • a[j]a[k]⼀组:
      • 如果a[i]单独⼀组的话,交换a[i]a[k],此时并不影响最终结果,和贪⼼解⼀致;
      • 如果a[i]a[l]⼀组的话,交换a[i]a[k],此时变成(a[i]+a[j]),(a[l]+a[k]),其中a[l]+a[k]<=a[j]+a[k]<=w,不影响最终结果,和贪⼼解⼀致。
        综上所述,我们可以通过不断的「调整」,使的最优解在「不改变其最优性」的前提下,变得和贪⼼解⼀致。那我们的贪⼼策略就等价于最优策略。
#include <bits/stdc++.h>
using namespace std;

const int N = 3e4 + 10;

int w, n;
int a[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> w >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort(a+1, a+1+n);

    int l = 1, r = n, ret = 0;
    while (l <= r)
    {
        if(a[l] + a[r] <= w) l++, r--;
        else r--;
        ret++;
    }
    cout << ret << endl;
    
    return 0;
}
P1056 [NOIP 2008 普及组] 排座椅 - 洛谷

由题意可得,我们会发现⼀些性质:

  • 设置横向通道的时候,并「不影响」左右相邻的同学;
  • 设置纵向通道的时候,并「不影响」上下相邻的同学。
    因此我们可以「分开」处理横向通道和纵向通道。
    处理横向通道(纵向同理,就不多赘述):
  • 收集每⼀⾏如果放上通道之后,会解决多少个交头接⽿的同学;
  • 对收集的信息「从⼤到⼩」排序,选最⼤的k ⾏就是最优结果。
#include <bits/stdc++.h>
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()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    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;
}
矩阵消除游戏

最优解是先暴⼒枚举所有⾏的选法,在⾏的选择都确定之后,再去贪⼼的处理列

#include <bits/stdc++.h>
using namespace std;

const int N = 20;

int n, m, k;
int a[N][N];
int col[N]; //统计列和

//统计x的二进制中1的个数
int calc(int x)
{
    int ret = 0;
    while (x)
    {
        ret++;
        x -= x & -x;
    }
    return ret;
}

//从大到小排序
bool cmp(int a, int b)
{
    return a > b;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m >> k;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];
    
    int ret = 0;
    //暴力枚举行所有选法
    for (int st = 0; st < (1 << n); st++)
    {
        int cnt = calc(st);
        if (cnt > k) continue; //不合法
        
        memset(col, 0, sizeof col);
        int sum = 0; //记录当前选法的和
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if ((st >> i) & 1) sum += a[i][j];
                else col[j] += a[i][j];
            }
        }
        
        //处理列
        sort(col, col + m, cmp);
        //选k - cnt列
        for (int j = 0; j < min(k - cnt, m); j++) sum += col[j];
        ret = max(ret, sum);
    }
    cout << ret << endl;
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

瑞萨RA4M2使用心得-KEIL5的第一次编译

目录 前言 环境&#xff1a; 开发板&#xff1a;RA-Eco-RA4M2-100PIN-V1.0 IDE&#xff1a;keil5.35 一、软件的下载 编辑瑞萨的芯片&#xff0c;除了keil5 外还需要一个软件&#xff1a;RASC 路径&#xff1a;Releases renesas/fsp (github.com) 向下找到&#xff1a; …

数据分析-Excel-学习笔记

Day1 复现报表聚合函数&#xff1a;日期联动快速定位区域SUMIF函数SUMIFS函数环比、同比计算IFERROR函数混合引用单元格格式总结汇报 拿到一个Excel表格&#xff0c;首先要看这个表格个构成&#xff08;包含了哪些数据&#xff09;&#xff0c;几行几列&#xff0c;每一列的名称…

整车CAN网络和CANoe

车载网络中主要包含有Can网络,Lin网络,FlexRay,Most,以太网。 500kbps:500波特率,表示的数据传输的速度。表示的是最大的网速传输速度。也就是每秒 500kb BodyCan车身Can InfoCan娱乐信息Can 车身CAN主要连接的是ESB电动安全带 ADB自适应远光灯等 PTCan动力Can 底盘Can

ChatGPT 的新图像生成器非常擅长伪造收据

本月&#xff0c;ChatGPT 推出了一种新的图像生成器&#xff0c;作为其 4o 模型的一部分&#xff0c;该模型在生成图像内的文本方面做得更好。 人们已经在利用它来生成假的餐厅收据&#xff0c;这可能会为欺诈者使用的已经很广泛的 AI 深度伪造工具包添加另一种工具。 多产的…

JS页面尺寸事件

元素位置 在这里插入图片描述 父元素带有定位时输出相对于父亲元素的距离值

网络协议之基础介绍

写在前面 本文看下网络协议相关基础内容。 1&#xff1a;为什么要有网络协议 为了实现世界各地的不同主机的互联互通。 2&#xff1a;协议的三要素 协议存在的目的就是立规矩&#xff0c;无规矩不成方圆嘛&#xff01;但是这个规矩也不是想怎么立就怎么立的&#xff0c;也…

初识数据结构——Java集合框架解析:List与ArrayList的完美结合

&#x1f4da; Java集合框架解析&#xff1a;List与ArrayList的完美结合 &#x1f31f; 前言&#xff1a;为什么我们需要List和ArrayList&#xff1f; 在日常开发中&#xff0c;我们经常需要处理一组数据。想象一下&#xff0c;如果你要管理一个班级的学生名单&#xff0c;或…

uniapp微信小程序引入vant组件库

1、首先要有uniapp项目&#xff0c;根据vant官方文档使用yarn或npm安装依赖&#xff1a; 1、 yarn init 或 npm init2、 # 通过 npm 安装npm i vant/weapp -S --production# 通过 yarn 安装yarn add vant/weapp --production# 安装 0.x 版本npm i vant-weapp -S --production …

贪心进阶学习笔记

反悔贪心 贪心是指直接选择局部最优解&#xff0c;不需要考虑之后的影响。 而反悔贪心是在贪心上面加了一个“反悔”的操作&#xff0c;于是又可以撤销之前的“鲁莽行动”&#xff0c;让整个的选择稍微变得“理智一些”。 于是&#xff0c;我个人理解&#xff0c;反悔贪心是…

Java 大视界 -- Java 大数据在航天遥测数据分析中的技术突破与应用(177)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

架构师面试(二十七):单链表

问题 今天的问题对于架构师来说会相对容易许多。今天出一个【数据结构与算法】相关的题目&#xff0c;醒醒脑。 给一张【单链表】&#xff0c;该单链表有100个节点元素&#xff08;当然&#xff0c;事先我们是不知道100这个数目的&#xff09;&#xff0c;要获取倒数第8个元素…

从扩展黎曼泽塔函数构造物质和时空的结构-15

回来考虑泽塔函数&#xff0c; 我们知道&#xff0c; 也就是在平面直角坐标系上反正切函数在x上的变化率&#xff0c;那么不难看出&#xff0c; 就是在s维空间上的“广义”反正切函数在单位p上的变化率&#xff0c;而泽塔函数&#xff0c;就是这些变化率的全乘积&#xff0c; 因…

01背包问题详解 具体样例模拟版

01背包 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 v i v_i vi​&#xff0c;价值是 w i w_i wi​。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 …

网络初识 - Java

网络发展史&#xff1a; 单机时代&#xff08;独立模式&#xff09; -> 局域网时代 -> 广域网时代 -> 移动互联网时代 网络互联&#xff1a;将多台计算机链接再一起&#xff0c;完成数据共享。 数据共享的本质是网络数据传输&#xff0c;即计算机之间通过网络来传输数…

每日一题(小白)回溯篇4

深度优先搜索题&#xff1a;找到最长的路径&#xff0c;计算这样的路径有多少条&#xff08;使用回溯&#xff09; 分析题意可以得知&#xff0c;每次向前后左右走一步&#xff0c;直至走完16步就算一条走通路径。要求条件是不能超出4*4的范围&#xff0c;不能重复之前的路径。…

k8s进阶之路:本地集群环境搭建

概述 文章将带领大家搭建一个 master 节点&#xff0c;两个 node 节点的 k8s 集群&#xff0c;容器基于 docker&#xff0c;k8s 版本 v1.32。 一、系统安装 安装之前请大家使用虚拟机将 ubuntu24.04 系统安装完毕&#xff0c;我是基于 mac m1 的系统进行安装的&#xff0c;所…

C++ STL 详解 ——list 的深度解析与实践指南

在 C 的标准模板库&#xff08;STL&#xff09;中&#xff0c;list作为一种重要的序列式容器&#xff0c;以其独特的双向链表结构和丰富的操作功能&#xff0c;在许多编程场景下发挥着关键作用。深入理解list的特性与使用方法&#xff0c;能帮助开发者编写出更高效、灵活的代码…

按键切换LCD显示后,显示总在第二阶段,而不在第一阶段的问题

这是一个密码锁的程序&#xff0c;当在输入密码后&#xff0c;原本是要重置密码&#xff0c;但是程序总是在输入密码正确后总是跳转置设置第二个密码&#xff0c;而第一个密码总是跳过。 不断修改后&#xff0c; 解决方法 将if语句换成switch语句&#xff0c;这样就可以分离程序…

护网蓝初面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…

C++11: 智能指针

C11: 智能指针 &#xff08;一&#xff09;智能指针原理1.RAll2.智能指针 (二)C11 智能指针1. auto_ptr2. unique_ptr3. shared_ptr4. weak_ptr &#xff08;三&#xff09;shared_ptr中存在的问题std::shared_ptr的循环引用 &#xff08;四&#xff09;删除器&#xff08;五&a…