洛谷P1083 [NOIP2012 提高组] 借教室 && 差分学习笔记
传送门:P1083 [NOIP2012 提高组] 借教室
"八骏日行三万里,穆王何事不重来。"
可惜啊,他再也没有回来……
题目大意:
给你每天能够租借的教室数量 和 几份租借申请
每份申请包含 租界时间(从第几天到第几天)和 每天需要租借的教室数量
问你能否满足所有的租借要求,如果不能,驳回一份最前的不能满足要求的申请
思路:
-
暴力:首先,这道题目的暴力很好想,就是从 1 开始遍历每一种订单,判断是否合法
复杂度:O(nm),铁定爆炸 -
线段树:蒟蒻第一眼看到这道题首先想到的是线段树,维护一个区间最小值,判断能否租借
每次有租借请求就做一个区间减法
但是一个黄题的话就有点。。。杀鸡焉用宰牛刀的感觉。其实是懒,而且要知道线段树复杂度还是很高的
具体可以看一下这个大佬的博文 -
至于正解:首先我们要明确一个知识点,那就是:
差分!
-
引入:
首先,给你一个问题:
给出 n 个数,再给出 Q 个询问,每个询问给出 l, r ;
要求你在 l 到 r 上每一个值都加上 x,而只给你 O(n) 的时间范围,怎么办? -
思路:
还是用上面这个题目,假如要在 l 和 r 上全都加一个 x,很显然,这个 O(n) 是不可避免的。
既然这样,那我们考虑在询问中我们不去来加,而是做一个标记,最后一起加上。有一点线段树lazy_tag的思想 -
ps:以上摘自https://www.zybuluo.com/Junlier/note/1232395
-
这就是差分
差分即相邻两个数的差。
我们可以这样去想:
对于一个序列 a[ ]={1,3,4,7};
我们构建一个差分数组 cf,cf[i] 表示 a[i] - a[i-1],则 cf[ ]={1,2,1,3};
这时,对于求某个数,比如:
a[1] = cf[1]; a[2] = cf[1] + cf[2]; a[3] = cf[1] + cf[2] + cf[3]; a[4] = cf[1] + cf[2] + cf[3] + cf[4];``
-
这时例如我们想要让 第 1 个数 和 第 3 个数 之间所有数 +3
发现:cf[1]+=3,但是 1~3 的区间内的数的两两间的差值没有变,a[3] 与 a[4] 之间的差值(cf[4])-3,a[4] 与之后的数差值不变
特殊 ——> 一般
对于在 l 到 r 的区间内 +x,其实相当于 cf[l]+x,cf[r+1]-x。
最后输出时,按照上面的方法顺序求解就好了。(区间减法亦然) -
顺便提一嘴,前缀和是用元数据求元与元之间的并集关系,而差分则是根据元与元之间的逻辑关系求元数据,是互逆思想。——摘自 皎月半洒花【小花】的题解
至此,我们就可以来探讨一下正解了 QWQ
那就是:差分 + 二分答案
-
一般来说,二分是个很有用的优化途径,因为这样会直接导致减半运算,而对于能否二分,有一个界定标准:状态的决策过程或者序列是否满足单调性或者可以局部舍弃性。 而在这个题里,因为如果前一份订单都不满足,那么之后的所有订单都不用继续考虑;而如果后一份订单都满足,那么之前的所有订单一定都可以满足,符合局部舍弃性,所以可以二分订单数量。 ——摘自 皎月半洒花【小花】的题解
-
二分查找の魔鬼细节,请移步
所以,每次二分一下,直到无法满足或者全枚举完结束。
代码:
不开 long long 见祖宗
#include<bits/stdc++.h> using namespace std; #define int long long const int maxn=1001000; int m,n; int r[maxn]; int rr[maxn]; int d[maxn],s[maxn],t[maxn]; int cf[maxn]; bool check(int x) { memset(cf,0,sizeof(cf)); for(int i=1;i<=x;i++) { cf[s[i]]+=d[i]; cf[t[i]+1]-=d[i]; } for(int i=1;i<=n;i++) { rr[i]=rr[i-1]+cf[i]; if(rr[i]>r[i])return 0; } return 1; } signed main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>r[i]; for(int i=1;i<=m;i++) cin>>d[i]>>s[i]>>t[i]; int left=1,right=m; while(left<right) { int mid=left+right>>1; if(check(mid))left=mid+1; else right=mid; //因为要查找的是右边界 } if(left<m)cout<<-1<<endl<<left<<endl; else cout<<0<<endl; return 0; }
后记:
这是一篇写了两个半小时的博文。。。
写的时候确实是挺煎熬的
但我觉得这很有意义
学会了自己之前不会的知识,了解了新的思路,更重要的是能 +rp
另外,这确实是一道非常好的题目!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!