【AcWing】蓝桥杯集训每日一题Day1|二分|差分|503.借教室(C++)

503. 借教室

503. 借教室 - AcWing题库
难度:简单
时/空限制:1s / 128MB
总通过数:8052
总尝试数:26311
来源:NOIP2012提高组
算法标签二分差分

题目内容

在大学期间,经常需要租借教室。
大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。
教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。 
面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来 n n n天的借教室信息,其中第 i i i天学校有 r i r_{i} ri个教室可供租借。
共有 m m m份订单,每份订单用三个正整数描述,分别为 d j , s j , t j d_{j},s_{j},t_{j} dj,sj,tj表示某租借者需要从第 s j s_{j} sj天到第 t s t_{s} ts天租借教室(包括第 s j s_{j} sj天和第 t j t_{j} tj天),每天需要租借 d j d_{j} dj个教室。 
我们假定,租借者对教室的大小、地点没有要求。
即对于每份订单,我们只需要每天提供 d j d_{j} dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。
如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。
这里的无法满足指从第 s j s_{j} sj天到第 t j t_{j} tj天中有至少一天剩余的教室数量不足 d j d_{j} dj个。 
现在我们需要知道,是否会有订单无法完全满足。
如果有,需要通知哪一个申请人修改订单。

输入格式

第一行包含两个正整数 n , m n,m n,m,表示天数和订单的数量。 
第二行包含 n n n个正整数,其中第 i i i个数为 r i r_{i} ri,表示第 i i i天可用于租借的教室数量。 
接下来有 m m m行,每行包含三个正整数 d j , s j , t j d_{j},s_{j},t_{j} dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。 
每行相邻的两个数之间均用一个空格隔开。
天数与订单均用从 11 开始的整数编号。

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数0。
否则(订单无法完全满足)输出两行,第一行输出一个负整数 −1,第二行输出需要修改订单的申请人编号。

数据范围

1 ≤ n , m ≤ 1 0 6 1≤n,m≤10^6 1n,m106
0 ≤ r i , d j ≤ 1 0 9 0≤r_{i},d_{j}≤10^9 0ri,dj109
1 ≤ s j ≤ t j ≤ n 1≤s_{j}≤t_{j}≤n 1sjtjn

输入样例:
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
输出样例:
-1
2
题目解析

数据范围在 1 0 6 10^6 106,时间复杂度要控制在 O ( n log ⁡ n ) O(n\log n) O(nlogn)/ O ( n ) O(n) O(n)
如何通过数据范围判断时间复杂度可以参考这篇文章
蓝桥杯暴力求解与高频考点-CSDN博客
如果直接模拟,不优化的话
![[Pasted image 20240309164127.png]]

可以从前往后依次遍历每个订单,可以开个数组,统计一下每一天一共需要多少教室
s … t s \dots t st天,每天需要 d d d个教室,可以枚举for循环一下,从s~t枚举一遍,cnt[i]+=d,每次加完之后,判断这一天需要教室数量是不是小于给定教室数量,如果是的话,表示满足;不是的话,表示不满足。
一共需要处理 1 0 6 10^6 106个订单,每个订单的区间长度最大 O ( n ) = 1 0 6 O(n)=10^6 O(n)=106,时间复杂度就是 1 0 6 ∗ 1 0 6 = 1 0 12 10^6*10^6=10^{12} 106106=1012,肯定会TLE(Time Limit Exceeded时间超限)。
如果代码不想超时,需要将时间复杂度控制在 1 0 8 10^8 108以内(1s是 1 0 8 10^8 108)

优化的话
如果按照题意进行动态地做,动态地给每一个区间加上同一个d,然后再去动态地判断这个区间内的某一个值是不是大于给定的值。

可以用线段树来做,可是线段树时间复杂度比较高,可能会TLE,线段树的题目n一般会给到 1 0 5 10^5 105。要使用比线段树常数更小的算法

1. 差分

如果把这个题变成一个判定性的问题,
判断一下能不能处理前k个订单,这个时候,这个题目就变成了给k个区间,每个区间加一个相同的数,加完之后需要计算一下每个数最终的值是多少
![[Pasted image 20240309164206.png]]

经典的模板题,相当于算法里的公式
797.差分
有一个标准做法,可以做到线性的时间复杂度 O ( n + m ) O(n+m) O(n+m)
判定性问题可以用差分来做

差分的原理

比如每一天可以用的教室数量是 r 1 … r n r_{1}\dots r_{n} r1rn,用数组r表示
要统计一下每天需要多少教室 a 1 … a n a_{1}\dots a_{n} a1an,用a数组来表示
初始的时候都是0,
给一个s~t的订单,给 a s … a t a_{s}\dots a_{t} asat之间的每个数全部加上一个d
先将a数组进行一个变形
a 0 = 0 a_{0}=0 a0=0
b 1 = a 1 − a 0 b_{1}=a_{1}-a_{0} b1=a1a0 b 2 = a 2 − a 1 b_{2}=a_{2}-a_{1} b2=a2a1以此类推,一直到 b n = a n − a n − 1 b_{n}=a_{n}-a_{n-1} bn=anan1
相当于对a数组进行一个差分变换
发现
如果给我们一个差分数组b,可以把原数组a反推回来
给原数组a,可以把差分数组b求出来
差分数组和原数组的信息量是一模一样的
差分数组推原数组
a 1 = b 1 + a 0 = b 1 a_{1}=b_{1}+a_{0}=b_{1} a1=b1+a0=b1
a 2 = b 2 + a 1 = b 1 + b 2 a_{2}=b_{2}+a_{1}=b_{1}+b_{2} a2=b2+a1=b1+b2
a n = b n + a n − 1 = b 1 + ⋯ + b n a_{n}=b_{n}+a_{n-1}=b_{1}+\dots+b_{n} an=bn+an1=b1++bn
原数组就是差分数组的前缀和数组

转换完之后
如果想对s~t,每个数加上一个d的话
如果要对原数组操作,要去修改 O ( n ) O(n) O(n)个数
但是这个操作对差分数组的影响就很少了
b 1 = a 1 − a 2 b 2 = a 2 − a 1 … b s − 1 = a s − 1 − a s − 2 b s = a s − a s − 1 b s + 1 = a s + 1 − a s … b t = a t − a t − 1 b t + 1 = a t + 1 − a t b t + 2 = a t + 2 − a t + 1 … \begin{array}{} b_{1}=a_{1}-a_{2} \\ b_{2}=a_{2}-a_{1} \\ \dots \\ b_{s-1}=a_{s-1}-a_{s-2} \\ b_{s}=a_{s}-a_{s-1} \\ b_{s+1}=a_{s+1}-a_{s} \\ \dots \\ b_{t}=a_{t}-a_{t-1} \\ b_{t+1}=a_{t+1}-a_{t} \\ b_{t+2}=a_{t+2}-a_{t+1} \\ \dots \end{array} b1=a1a2b2=a2a1bs1=as1as2bs=asas1bs+1=as+1asbt=atat1bt+1=at+1atbt+2=at+2at+1
这个操作只会对 a s … a t a_{s}\dots a_{t} asat将每个数全部加上d
因此
b 1 … b s − 1 b_{1}\dots b_{s-1} b1bs1都是不变的,因为 a 0 … a s − 1 a_{0}\dots a_{s-1} a0as1不变,所以差值也不变
b s b_{s} bs的话, a s a_{s} as加上一个d, a s − 1 a_{s-1} as1不变,所以 b s b_{s} bs加了一个d
b s + 1 … b t b_{s+1}\dots b_{t} bs+1bt,由于 a s … a t a_{s}\dots a_{t} asat每个数全部加了一个d,所以差值是不变的,所以b都是不变的
b t + 1 b_{t+1} bt+1的话, a t + 1 a_{t+1} at+1不变, a t a_{t} at加了一个d,所以差值会减一个d,所以 b t + 1 b_{t+1} bt+1减一个d
b t + 2 b_{t+2} bt+2开始到后面的,因为从 a t + 1 … a n a_{t+1}\dots a_{n} at+1an一直都不变,所以不变
不变 { b 1 = a 1 − a 2 b 2 = a 2 − a 1 … b s − 1 = a s − 1 − a s − 2 + d b s = a s − a s − 1 不变 { b s + 1 = a s + 1 − a s … b t = a t − a t − 1 − d b t + 1 = a t + 1 − a t 不变 { b t + 2 = a t + 2 − a t + 1 … \begin{array}{} \\ 不变\begin{cases} b_{1}=a_{1}-a_{2} \\ b_{2}=a_{2}-a_{1} \\ \dots \\ b_{s-1}=a_{s-1}-a_{s-2} \\ \end{cases}\\ +d\qquad b_{s}=a_{s}-a_{s-1} \\ 不变\begin{cases} b_{s+1}=a_{s+1}-a_{s} \\ \dots \\ b_{t}=a_{t}-a_{t-1} \\ \end{cases}\\ -d\qquad b_{t+1}=a_{t+1}-a_{t} \\ 不变\begin{cases} b_{t+2}=a_{t+2}-a_{t+1} \\ \dots \end{cases} \end{array} 不变 b1=a1a2b2=a2a1bs1=as1as2+dbs=asas1不变 bs+1=as+1asbt=atat1dbt+1=at+1at不变{bt+2=at+2at+1

对于原数组的操作,本来要操作 O ( n ) O(n) O(n)个数,对于差分数组的影响,只会影响 b s b_{s} bs b t + 1 b_{t+1} bt+1两个数,这样就可以将修改操作从 O ( n ) O(n) O(n)变成 O ( 1 ) O(1) O(1)
改变 O ( n ) O(n) O(n)个数,需要两层循环,现在只用改变两个数,就不用写循环了,可以把时间复杂度从 O ( n 2 ) O(n^2) O(n2)变成 O ( n ) O(n) O(n)
这样的话每个操作就不要对原数组操作,而是对差分数组进行操作,差分数组求完之后,再通过差分数组求出来原数组,相当于做一个变换

这样可以用 O ( n + m ) O(n+m) O(n+m)的时间复杂度,判断出前k个订单有没有出现矛盾

2. 二分

原问题
把所有的订单取出来,编号是1~m
先到先得,按照编号顺序依次处理每个订单

二段性

这些个能处理的订单,有一个二段性
比如说在这个问题里面,第k个订单是最后一个能处理的订单
从第k+1个订单开始就不能处理了
![[Pasted image 20240309185859.png]]

把前k个区间全部加到数组上,每一天需要的教室数量都是小于等于给定的教室数量,对于k前面的任何一个数x,同样能处理x个订单,显然是成立的
前x个订单严格比前k个订单少一些区间,相当于在某些数上少加了一些d,加上那些之后不会超过给定值,不加那些数同样不会超过给定值
最后一个能处理的订单是第k个订单的话,对于小于等于k等任何一个数x,前x个订单一定都是可以处理的

同理

第一个不能处理的订单是第k+1个订单的话,如果把前k+1个区间加到天数数组r上的话,一定会有某一天,它需要的教室数量大于给定值
对于大于等于k+1的任何一个x,1~x一定也都是不能处理的
![[Pasted image 20240309185935.png]]

因为前k+1个区间加完之后,在某一天已经大于给定值了,再加上一些额外的数,仍然会大于给定值


对于x来说,当x取1~k之间的数的时候,它是满足的
当x大于k的时候,是不满足的

具有二段性的话,就可以通过二分把这个边界二分出来,
789.数的范围

如何二分

比如最后一个能处理的订单是第k个订单,
左右边界是1~m
每次二分一个中点mid
如果1~mid,能满足的话,说明最后一个能满足的订单在mid或者在mid的右边,所以答案会落在 [ m i d , m ] [mid,m] [mid,m]这个区间,
![[Pasted image 20240309191741.png]]

就可以删掉左边的区间,把二分的左端点L设成mid
![[Pasted image 20240309191817.png]]

再二分中点
取一个mid,如果处理mid这个订单会出现矛盾,说明1~mid是无法处理的,说明最后一个能处理的订单在mid的严格左边
![[Pasted image 20240309190835.png]]

因此相当于把右边的区间删掉,把右端点R置成mid-1
![[Pasted image 20240309190918.png]]

每次将搜索范围缩小一半,缩小完之后保证答案一定在搜索范围内,最后当范围内只有一个数的时候,它就是答案

二分模板用哪个,取决于mid属于左半边区间还是右半边区间,取整的问题,二分的话,整数不一定能够刚好整除,奇数的话是上取整还是下取整,如果模板用错可能会有死循环

代码
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;typedef long long LL;
//算前缀和,10^6个10^9,有可能会爆int,需要用到long longconst int N = 1000010;int n, m;
int w[N];
//用w存r,不用r,因为二分的时候会用到变量r,防止变量冲突
int d[N], s[N], t[N];
LL b[N];//检查前mid订单能不能满足
bool check (int mid)
{memset(b, 0, sizeof b); //先把差分数组初始化为0,不能让上次检查的结果,影响下次检查的过程for (int i = 1; i <= mid; i++) //依次处理每一个订单{b[s[i]] += d[i];b[t[i] + 1] -= d[i];}LL s = 0;  //用一个前缀和来计算当前的每一a_i是多少for (int i = 1; i <= n; i++) //以此类推每一天{s += b[i]; //每次更新一下前缀和if (s > w[i]) return false;  //发现当前教室数量大于给定教室数量,return false}return true;  //否则return true
}int main()
{scanf("%d%d", &n, &m);for (int 1 = 1; i <= n; i++) scanf("%d", &w[i]);//依次读入每天可以用的教室数量for (int i = 1; i <= m; i++) scanf("%d%d%d", &d[i], &s[i], &t[i]);//依次读入m个订单d,s,t,订单数量和起始时间//二分int l = 0; r = m;   //0表示一个订单都不能满足while (l < r){//求中点int mid = l + r + 1 >> 1; //因为l=mid,+1用上取整;如果r=mid,用下取整if (check(mid)) l = mid;  //如果发现[1,mid)可以满足,表示答案应该在[mid,m]else r = mid -1;}if (r == m) puts("0"); //如果发现所有订单都能满足,输出0else printf("-1\n%d\n", r + 1); //否则第一行输出-1,第二行输出第一个不能满足的订单编号return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【吊打面试官系列】Java虚拟机JVM篇 - 关于内存溢出

大家好&#xff0c;我是锋哥。今天分享关于内存溢出的JVM面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是内存溢出&#xff1f; 内存溢出&#xff08;OOM&#xff09;是指可用内存不足。程序运行需要使用的内存超出最大可用值&#xff0c;如果不进行处理就会影响到其他…

Django添加app

Django添加App python manage.py startapp [app_name]快速上手 注册app&#xff0c;setting.py 编写url和视图的对应关系 添加视图函数 命令行启动 python manage.py runserver页面模板

七、软考-系统架构设计师笔记-数据库设计基础知识

1、数据库基础概念 数据库基本概念 数据(Data)数据库(Database)数据库管理系统(DBMS)数据库系统(DBS) 1.数据(Data) 是数据库中存储的基本对象&#xff0c;是描述事物的符号记录。 数据的种类&#xff1a; 文本、图形、图像、音频、视频等。 2.数据库(Database, DB) 数据库…

LeetCode Python - 44.通配符匹配

目录 题目答案运行结果 题目 给你一个输入字符串 (s) 和一个字符模式 &#xff0c;请你实现一个支持 ‘?’ 和 ‘’ 匹配规则的通配符匹配&#xff1a; ‘?’ 可以匹配任何单个字符。 ’ 可以匹配任意字符序列&#xff08;包括空字符序列&#xff09;。 判定匹配成功的充要…

数字化审计智慧

简析内部审计数字化转型的方法和路径 内部审计是一种独立的、客观的确认和咨询活动&#xff0c;包括鉴证、识别和分析问题以及提供管理建议和解决方案。狭义的数字化转型是指将企业经营管理和业务操作的各种行为、状态和结果用数字的形式来记录和存储&#xff0c;据此再对数据进…

Excel 一列数据转换为多行数据

目录 一. 需求二. 填充 替换 进行转换 一. 需求 ⏹有如下图所示的数据&#xff0c;现在要求按每5行为一组&#xff0c;转换为一行 二. 填充 替换 进行转换 先手动将一列中的1到5行的单元格号转换为一行再手动将一列中的6到10行的单元格号转换为一行然后填充最后使用替换功能…

为什么选择 Flink 做实时处理

优质博文&#xff1a;IT-BLOG-CN 为什么选择 Flink 【1】流数据更真实地反映了我们的生活方式&#xff08;实时聊天&#xff09;&#xff1b; 【2】传统的数据架构是基于有限数据集的&#xff08;Spark 是基于微批次数据处理&#xff09;&#xff1b; 【3】我们的目标&#xf…

算法(递归)黑盒思想

递归vs搜索vs回溯 递归的时候其实就是在搜索&#xff0c;递归返回的时候其实就是在回溯 常见的二叉树的题目基本都用到了递归&#xff1a; 求二叉树节点个数&#xff08;后序遍历&#xff09; int BinaryTreeSize(BTNode* root) {return root NULL ? 0 : BinaryTreeSize(roo…

爬虫(五)

1. 前端JS相关 三元运算 v1 条件 ? 值A : 值B; # 如果条件成立v1值A&#xff0c;不成立v1等于值Bres 1 1 ? 99 : 88 # res99特殊的逻辑运算 v1 11 || 22 # Ture v2 9 || 14 # 9 v3 0 || 15 # 15 v3 0 || 15 || "zhangfei" # 15赋值和…

使用Migration升级数据库

使用Migration升级数据库 package com.tiger.room2;import android.content.Context;import androidx.annotation.NonNull; import androidx.room.Database; import androidx.room.Room; import androidx.room.RoomDatabase; import androidx.room.migration.Migration; impo…

嵌入式 Linux 学习

在学习嵌入式 Linux 之前&#xff0c;我们先来了解一下嵌入式 Linux 有哪些东西。 1. 嵌入式 Linux 的组成 嵌入式 Linux 系统&#xff0c;就相当于一套完整的 PC 软件系统。 无论你是 Linux 电脑还是 windows 电脑&#xff0c;它们在软件方面的组成都是类似的。 我们一开电…

2023护网蓝初面试

目录 一、渗透测试的流程 二、常见的漏洞 三、中间件漏洞 四、SQL注入原理、种类&#xff1f;防御&#xff1f;预编译原理&#xff0c;宽字节注入原理 预编译原理&#xff1a; 宽字节注入原理&#xff1a; 五、XSS的种类有哪些&#xff1f;区别&#xff1f;修复&#xf…