NO.66十六届蓝桥杯备战|基础算法-贪心-区间问题|凌乱的yyy|Rader Installation|Sunscreen|牛栏预定(C++)

news2025/5/11 13:32:39

区间问题是另⼀种⽐较经典的贪⼼问题。题⽬⾯对的对象是⼀个⼀个的区间,让我们在每个区间上做出取舍。
这种题⽬的解决⽅式⼀般就是按照区间的左端点或者是右端点排序,然后在排序之后的区间上,根据题⽬要求,制定出相应的贪⼼策略,进⽽得到最优解。
具体是根据左端点还是右端点排序?升序还是降序?⼀般是假设⼀种排序⽅式,并且制定贪⼼策略,当没有明显的反例时,就可以尝试去写代码。

P1803 凌乱的yyy / 线段覆盖 - 洛谷

按照区间左端点从⼩到⼤排序,当两个区间「重叠」的时候,我们必须要舍弃⼀个。为了能够「在移除某个区间后,保留更多的区间」,我们应该把「区间范围较⼤」的区间移除。
因此以第⼀个区间为基准,遍历所有的区间:

  • 如果重叠,选择「最⼩的右端点」作为新的基准;
  • 如果不重叠,那么我们就能多选⼀个区间,以「新区间为基准」继续向后遍历。

可以⽤「交换论证法」证明我们的贪⼼策略是最优解:
在从前往后扫描的过程中,当贪⼼解和最优解第⼀次出现不同决策时,关于两个区间a,b(其中a在左,b在右)的取舍,有下⾯两种情况:

  1. a, b两个区间不重叠:
  • 贪⼼解会将a 区间保留,然后以b 区间为基准,继续向后对⽐别的区间;
  • 最优解的选择有下⾯⼏种情况:
    a. 舍弃⼀个区间,那么必定不如贪⼼解,⽭盾。
    b. 以a区间为基准,向后对⽐。那更夸张了,我们已经按照区间左端点从⼩到⼤排好序了,如果a,b不重叠,那么a与后⾯的所有区间都不重叠,⽤a作为基准没有⼀点意义。还会选出与b重叠的区间。
    综上,如果「不重叠」的话,贪⼼解和最优解的决策应该是「⼀致」的。
  1. a, b 两个区间重叠,那么⽆论什么解,都需要「舍弃」⼀个区间:
  • 贪⼼解会保留两者「右端点较⼩」的区间,舍弃「右端点较⼤」的区间;
  • 最优解的选择就是,保留「右端点较⼤」的区间,舍弃「右端点较⼩」的区间。
    如果第⼆种决策能在此基础上得到最优解,那么我们把「右端点较⼤」区间换成「右端点较⼩」的区间是不受影响的。
    因为「较⼤区间」都和后续选择的区间「没有重叠」,这个较⼩的区间也必定「没有重叠」。因此,最优解可以调整成贪⼼解。
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;

int n;
struct node
{
    int l, r;
}a[N];

bool cmp(node& x, node& y)
{
    return x.l < y.l;
}

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

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].l >> a[i].r;
    
    sort(a+1, a+1+n, cmp);

    int ret = 1;
    int r = a[1].r; //以第一个区间为基准
    for (int i = 2; i <= n; i++)
    {
        int left = a[i].l, right = a[i].r;
        if (left < r) //有重叠
        {
            r = min(r, right);
        }
        else
        {
            ret++;
            r = right;
        }
    }
    cout << ret << endl;
    
    return 0;
}
UVA1193 Radar Installation - 洛谷

如图所⽰,当⼀个岛屿的「坐标」已知,其实可以计算出:当雷达放在x轴的「哪段区间」时,可以覆盖到这个岛屿
![[Pasted image 20250405192831.png]]

根据「勾股定理」得:ax的⻓度为 l = d 2 − y 2 l=\sqrt{ d^{2}-y^2 } l=d2y2 ,那么雷达所处的范围就是 [ x − l , x + l ] [x-l,x+l] [xl,x+l]。因此,针对每⼀个岛屿,我们都可以算出⼀个「能够覆盖它的区间」。
原问题就变成给定⼀些区间,所有互相重叠的区间⼀共有多少组。
按照区间「左端点从⼩到⼤」排序,当两个区间「重叠」的时候,为了后⾯能够「尽可能多的选出互相重叠的区间」,我们应该把「区间范围较⼤」的区间移除,因为选择较⼤区间会造成选出来的区间「不是互相重叠」的。
因此以第⼀个区间为基准,遍历所有的区间:

  • 如果重叠,选择「最⼩的右端点」作为新的基准;
  • 如果不重叠,那么我们就能多选⼀个区间,以「新区间为基准」继续向后遍历。
    可以⽤「反证法」证明,所有区间按照按照「左端点」排序之后,「互相重叠的区间」都是「相邻」的:
    假设所有区间按照左端点排序之后,存在互相重叠的区间,它们是不相邻的。也就是存在a,b,c,d四个区间,其中a,b,d互相重叠,但是a,b,c与它们三个不是互相重叠。
    设a, b, d区间重叠部分的范围是[x, y],那么c 的位置有两种情况:
  • c 在[x, y]的左侧,与实际不符:
    因为如果c在左侧,⼜要与a,b不是互相重叠,那么c的右端点必须要⼩于b的左端点,那就与所有线段按照左端点排序不符;
  • c 在[x, y]的右侧,也与实际不符:
    因为如果c在右侧,⼜要与a,b不是互相重叠,那么c的左端点必须要⼤于a,b的右端点的最⼩值;⼜因为d与a,b互相重叠,d的左端点就要⼩于a,b的右端点的最⼩值,与所有区间按照左端点排序不符。
    综上所述,所有区间按照按照「左端点」排序之后,「互相重叠的区间」都是「相邻」的
#include <bits/stdc++.h>
using namespace std;

const int N = 1010;

int n;
double d;
struct node
{
    double l, r;
}a[N];

bool cmp(node& x, node& y)
{
    return x.l < y.l;    
}

int main()
{
    int cnt = 0;
    while (cin >> n >> d, n && d)
    {
        cnt++;
        bool flg = false;
        for (int i = 1; i <= n; i++)
        {
            double x, y; cin >> x >> y;
            if (y > d) flg = true;
            double l = sqrt(d * d - y * y);
            a[i].l = x - l, a[i].r = x + l;
        }
        
        cout << "Case " << cnt << ": ";
        if (flg) cout << -1 << endl;
        else
        {
            sort(a+1, a+n+1, cmp);
            int ret = 1;
            double r = a[1].r;
            for (int i = 2; i <= n; i++)
            {
                double left = a[i].l, right = a[i].r;
                if (left <= r)
                {
                    r = min(r, right);
                }
                else
                {
                    ret++;
                    r = right;
                }
            }
            cout << ret << endl;
        }
    }

    return 0;
}
P2887 [USACO07NOV] Sunscreen G - 洛谷

思考具体解法,从下⾯的情况中,筛选出没有特别明显的反例的组合:

  1. 区间按照左端点从⼩到⼤+防晒霜从⼩到⼤(优先选⼩);
  2. 区间按照左端点从⼩到⼤+防晒霜从⼤到⼩(优先选⼤);
  3. 区间按照左端点从⼤到⼩+防晒霜从⼩到⼤(优先选⼩);
  4. 区间按照左端点从⼤到⼩+防晒霜从⼤到⼩(优先选⼤);
  5. 区间按照右端点从⼩到⼤+防晒霜从⼩到⼤(优先选⼩);
  6. 区间按照右端点从⼩到⼤+防晒霜从⼤到⼩(优先选⼤)。
  7. 区间按照右端点从⼤到⼩+防晒霜从⼩到⼤(优先选⼩);
  8. 区间按照右端点从⼤到⼩+防晒霜从⼤到⼩(优先选⼤)。
    虽然看似很多,但是很容易在错误的策略中举出「反例」。
  • 区间按照左端点从小到大,优先选较小的点:
    ![[Pasted image 20250405210201.png]]

第一个区间选了a之后,b这个点就没办法分配了
实际上应该把a给第二个区间,b给第一个区间

  • 区间按照左端点从小到大,优先选较大的点:
    ![[Pasted image 20250405210416.png]]

第一个区间选了b之后,a这个点就没办法分配了
实际上应该把b给第二个区间,a给第一个区间

  • 区间按左端点从大到小,优先选较小的点:
    ![[Pasted image 20250405210732.png]]

第一个区间选了a之后,b这个点就没办法分配了
实际上应该把a给第二个区间,b给第一个区间

  • 区间按左端点从大到小,优先选较大的点:
    ![[Pasted image 20250405211155.png]]

较小的点能够更好地被后面的区间选择

  • 区间按右端点从小到大,优先选较小的点:
    较大的点能够更好地被后面的区间选择
  • 区间按照右端点从小到大,优先选较大的点:
    ![[Pasted image 20250405211529.png]]

第一个区间选了b之后,a这个点就没办法分配了
实际上应该把b给第二个区间,a给第一个区间

  • 区间按右端点从大到小,优先选较小的点:
    ![[Pasted image 20250405212006.png]]

把a给了第一个区间以后,b就没办法分配了
实际上应该把b给第一个区间,a给第二个区间

  • 区间按右端点从大到小,优先选较大的点:
    ![[Pasted image 20250405212450.png]]

第一个区间选了b之后,a这个点就没办法分配了
实际上应该把b给第二个区间,a给第一个区间

综上所述,有两种组合没有明显的反例,分别是:

  1. 区间按照「左端点从⼤到⼩」排序,防晒霜从⼤到⼩排序,「优先选择较⼤」的防晒霜;
  2. 区间按照「右端点从⼩到⼤」排序,防晒霜从⼩到⼤排序,「优先选择较⼩」的防晒霜。
    实际上两种情况都是正确的,我们取其⼀证明⼀下,另⼀种证明⽅式类似。

可以⽤「交换论证法」证明⽅式⼆的正确性:
从前往后依次⽐较「贪⼼解」和「最优解」针对每⼀个区间的决策,当找到第⼀个区间,它们的「分配决策不同」时:设贪⼼解⽤的是a防晒霜,最优解⽤的是b防晒霜,因为贪⼼解选的是最⼩的,所以有 a ≤ b a \le b ab
此时关于a 防晒霜的使⽤情况,可以分以下⼏种情况:

  1. a防晒霜在「最优解中没有使⽤」,那么我们可以直接⽤a防晒霜替换b防晒霜,此时最优解的「最优性」并没有损失,那么贪⼼解就和最优解决策⼀致;
  2. a防晒霜在「最优解的后续决策中使⽤了」,设b使⽤的区间是 [ x b , y b ] [x_{b},y_{b}] [xb,yb],a使⽤的区间是 [ x a , y a ] [x_{a},y_{a}] [xa,ya]
    易得:
    x b ≤ b ≤ y b , x a ≤ a ≤ y a x_{b} \le b \le y_{b}, x_{a} \le a \le y_{a} xbbyb,xaaya
    因为我们是按照右端点从⼩到⼤排序的,所以 y a ≥ y b y_{a} \ge y_{b} yayb
    综上:
    x a ≤ a ≤ b ≤ y b ≤ y a x_{a} \le a \le b \le y_{b} \le y_{a} xaabybya
    所以b也可以作⽤于「a作⽤的区间」,也就是在最优解中「a,b可以互换」,进⽽就转换成贪⼼解。
    因此,针对每⼀个位置,我们都可以把最优解在「不失去其最优性的前提下」,转化成「贪⼼解」
#include <bits/stdc++.h>
using namespace std;

const int N = 2510;

int n, m;
struct node
{
    int x;
    int y;
}a[N], b[N];

bool cmp(node& x, node& y)
{
    return x.x > y.x;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y;
    for (int i = 1; i <= m; i++) cin >> b[i].x >> b[i].y;

    sort(a+1, a+1+n, cmp); //按左端点从大到小排序
    sort(b+1, b+1+m, cmp); //按阳光强度从大到小排序

    int ret = 0;
    for (int i = 1; i <= n; i++)
    {
        int l = a[i].x, r = a[i].y;
        for (int j = 1; j <= m; j++)
        {
            int w = b[j].x, &cnt = b[j].y;
            if (cnt == 0) continue;
            if (w < l) break;
            if (w > r) continue;
            ret++;
            cnt--;
            break;
        }
    }
    cout << ret << endl;
    
    return 0;
}
P2859 [USACO06FEB] Stall Reservations S - 洛谷

按照「起始时间」对所有奶⽜「从⼩到⼤」排序,然后「从前往后」依次安排每⼀头奶⽜,设这头奶⽜的产奶的时间区间是[a, b]

  • 在已经有⽜的所有⽜棚⾥,如果「结束时间⼩于a」,就可以把这头奶⽜放在这个⽜棚⾥⾯;如果有很多符合要求的,可以随便找⼀个。因为我们是按照起始时间从⼩到⼤排序,只要这些⽜棚都符合要求,对于后⾯的奶⽜⽽⾔也都符合要求。不妨找结束时间最早的,⽅便判断。
  • 如果所有已经有⽜的⽜棚的「结束时间都⼤于a 」,那么这头⽜只能⾃⼰单独开⼀个⽜棚。
    这个贪⼼策略是⽐较符合我们「常识」的,尽量优的安排每⼀头⽜
#include <bits/stdc++.h>
using namespace std;

const int N = 5e4 + 10;

int n;
struct node
{
    int x; //起始时间 / 结束时间
    int y; //终止时间 / 牛棚编号
    int z; //排序之前的编号

    bool operator<(const node& a) const
    {
        return x > a.x;
    }

}a[N];

int ret[N]; //存最终结果

bool cmp(node& x, node& y)
{
    return x.x < y.x;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].x >> a[i].y;
        a[i].z = i;
    }
    sort(a+1, a+1+n, cmp);

    int num = 1;
    priority_queue<node> heap;
    ret[a[1].z] = 1;
    heap.push({a[1].y, 1});
    
    for (int i = 2; i <= n; i++)
    {
        int l = a[i].x, r = a[i].y;
        if (l <= heap.top().x) //无法放在已经分配的牛棚里
        {
            num++;
            ret[a[i].z] = num;
            heap.push({r, num});
        }
        else
        {
            node t = heap.top(); heap.pop();
            ret[a[i].z] = t.y;
            heap.push({r, t.y});
        }
    }
    cout << num << endl;
    for (int i = 1; i <= n; i++) cout << ret[i] << endl;
    
    return 0;
}

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

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

相关文章

搭建redis主从同步实现读写分离(原理剖析)

搭建redis主从同步实现读写分离(原理剖析) 文章目录 搭建redis主从同步实现读写分离(原理剖析)前言一、搭建主从同步二、同步原理 前言 为什么要学习redis主从同步&#xff0c;实现读写分析。因为单机的redis虽然是基于内存&#xff0c;单机并发已经能支撑很高。但是随着业务量…

Rust切片、结构体、枚举

文章目录 切片类型字符串切片其他结构的切片 结构体结构体实例元组结构体结构体所有权输出结构体结构体的方法结构体关联函数单元结构体 枚举match语法Option枚举类if let 语句 切片类型 切片&#xff08;Slice&#xff09;是对数据值的部分“引用” 我们可以从一个数据集合中…

【学习笔记】深度学习环境部署相关

文章目录 [AI硬件科普] 内存/显存带宽&#xff0c;从 NVIDIA 到苹果 M4[工具使用] tmux 会话管理及会话持久性[A100 02] GPU 服务器压力测试&#xff0c;gpu burn&#xff0c;cpu burn&#xff0c;cuda samples[A100 01] A100 服务器开箱&#xff0c;超微平台&#xff0c;gpu、…

股票日数据使用_未复权日数据生成前复权日周月季年数据

目录 前置&#xff1a; 准备 代码&#xff1a;数据库交互部分 代码&#xff1a;生成前复权 日、周、月、季、年数据 前置&#xff1a; 1 未复权日数据获取&#xff0c;请查看 https://blog.csdn.net/m0_37967652/article/details/146435589 数据库使用PostgreSQL。更新日…

【LeetCode Solutions】LeetCode 146 ~ 150 题解

CONTENTS LeetCode 146. LRU 缓存&#xff08;中等&#xff09;LeetCode 147. 对链表进行插入排序&#xff08;中等&#xff09;LeetCode 148. 排序链表&#xff08;中等&#xff09;LeetCode 149. 直线上最多的点数&#xff08;困难&#xff09;LeetCode 150. 逆波兰表达式求值…

《 如何更高效地学习》

&#x1f680;个人主页&#xff1a;BabyZZの秘密日记 &#x1f4d6;收入专栏&#xff1a;个人谈心 &#x1f30d;文章目入 一、明确学习目标二、制定学习计划三、选择合适的学习方法&#xff08;一&#xff09;主动学习&#xff08;二&#xff09;分散学习&#xff08;三&#…

分布式数据一致性场景与方案处理分析|得物技术

一、引言 在经典的CAP理论中一致性是指分布式或多副本系统中数据在任一时刻均保持逻辑与物理状态的统一&#xff0c;这是确保业务逻辑正确性和系统可靠性的核心要素。在单体应用单一数据库中可以直接通过本地事务(ACID)保证数据的强一致性。 然而随着微服务架构的普及和业务场…

JAVA:使用 Curator 进行 ZooKeeper 操作的技术指南

1、简述 Apache Curator 是一个基于 ZooKeeper 的 Java 客户端库&#xff0c;它极大地简化了使用 ZooKeeper 的开发工作。Curator 提供了高层次的 API&#xff0c;封装了很多复杂的 ZooKeeper 操作&#xff0c;例如连接管理、分布式锁、Leader 选举等。 在分布式系统中&#…

Linux中的调试器gdb与冯·诺伊曼体系

一、Linux中的调试器&#xff1a;gdb 1.1安装与版本查看 可以使用yum进行安装&#xff1a; yum install -y gdb 版本查看&#xff1a;使用指令 gdb --version 1.2调试的先决条件&#xff1a;release版本与debug版本的切换 debug版本&#xff1a;含有调试信息 release版本…

STM32 + keil5 跑马灯

硬件清单 1. STM32F407VET6 2. STLINK V2下载器(带线) 环境配置 1. 安装ST-LINK 2. 安装并配置 keil5 https://blog.csdn.net/qq_36535414/article/details/108947292 https://blog.csdn.net/weixin_43732386/article/details/117375266 3. 接线并下载 点击"LOAD“&a…

盲盒小程序开发平台搭建:打造个性化、高互动性的娱乐消费新体验

在数字化浪潮席卷消费市场的今天&#xff0c;盲盒小程序以其独特的趣味性和互动性&#xff0c;迅速成为了年轻人追捧的娱乐消费新宠。盲盒小程序不仅为用户带来了拆盒的惊喜和刺激&#xff0c;更为商家提供了创新的营销手段。为了满足市场对盲盒小程序日益增长的需求&#xff0…

DuckDB系列教程:如何分析Parquet文件

Parquet 是一种强大的、基于列的存储格式&#xff0c;适用于实现更快捷和更高效的数据分析。您可以使用 DuckDB 这种内存型分析数据库来处理 Parquet 文件并运行查询以对其进行分析。 在这篇文章中&#xff0c;我们将逐步介绍如何使用 DuckDB 对存储在 Parquet 文件中的餐厅订单…

GRE,MGRE

GRE&#xff1a;静态过程&#xff0c;有局限性 R1 &#xff1a; [r1]interface Tunnel 0/0/0 --- 创建一个虚拟的隧道接口 [r1-Tunnel0/0/0]ip address 192.168.3.1 24 --- 给隧道接口分配一个 IP 地址 [r1-Tunnel0/0/0]tunnel-protocol gre --- 定义接口的封装方式 [r1-Tun…

【linux学习】linux系统调用编程

目录 一、任务、进程和线程 1.1任务 1.2进程 1.3线程 1.4线程和进程的关系 1.5 在linux系统下进程操作 二、Linux虚拟内存管理与stm32的真实物理内存区别 2.1 Linux虚拟内存管理 2.2 STM32的真实物理内存映射 2.3区别 三、 Linux系统调用函数 fork()、wait()、exec(…

Azure Speech 赋能,为智能硬件注入 AI 语音 “新灵魂”

在人工智能技术飞速发展的今天&#xff0c;智能硬件正逐步渗透到人们生活的方方面面。AI玩具、AI眼镜、AI鼠标等创新产品不仅提升了用户体验&#xff0c;更带来了前所未有的交互方式。领驭科技凭借微软Azure Speech的强大技术能力&#xff0c;为硬件厂商提供一站式AI语音解决方…

SignalR给特定User发送消息

1、背景 官网上SignalR的demo很详细&#xff0c;但是有个特别的问题&#xff0c;就是没有详细阐述如何给指定的用户发送消息。 2、解决思路 网上整体解决思路有三个&#xff1a; 1、最简单的方案&#xff0c;客户端连接SignalR的Hub时&#xff0c;只是简单的连接&#xff0c…

React: hook相当于函数吗?

一、Hook 是一个函数&#xff0c;但不仅仅是函数 函数的本质 Hook 确实是一个 JavaScript 函数&#xff0c;例如 useState、useEffect 或自定义 Hook 都是函数。它们可以接受参数&#xff08;如初始状态值或依赖项数组&#xff09;&#xff0c;并返回结果&#xff08;如状态值和…

【数据分享】2002-2023中国湖泊水位变化数据集(免费获取)

湖泊水位变化是研究水资源动态、生态系统演变和气候变化影响的重要指标。湖泊水位的升降不仅反映了降水、蒸发和入流水量的变化&#xff0c;还与人类活动、气候波动及地质过程密切相关。因此&#xff0c;高精度、长时间序列的湖泊水位数据对于水资源管理、洪水预测以及生态环境…

免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制

摘要 本文旨在探讨基于SSM框架和Android Studio的游戏搜索App的设计与实现。首先&#xff0c;我们详细介绍了SSM框架&#xff0c;这是一种经典的Java Web开发框架&#xff0c;由Spring、SpringMVC和MyBatis三个开源项目整合而成&#xff0c;为开发企业级应用提供了高效、灵活、…

STM32单片机入门学习——第14节: [6-2] 定时器定时中断定时器外部时钟

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.04 STM32开发板学习——第14节: [6-2] 定时器定时中断&定时器外部时钟 前言开发…