2012 D2 T2 借教室 题解

皎月半洒花

2018-02-07 19:54:03

Solution

## 一、暴力简述 [甩链接.jpeg](https://www.luogu.org/problemnew/show/P1083#sub) 首先我们不难看出,这道题————并不是一道多难的题,因为显然,第一眼看题目时便很容易地想到暴力如何打:枚举每一种订单,然后针对每一种订单,对区间内的每一天进行修改(做减法),直到某一份订单使得某一天剩下的教室数量为负数,即可得出结果。 先小小的评析一下吧:**凡是能打出几近正解的暴力题,都不是难题!**(蒟蒻可以骗到50+的不就是水题吗qwq)但是,显然枚举形式的暴力会很慢,期望的时间复杂度约为 ### $O(m \times n)$, 可能会更快一些(~~但没卵用qwq~~) ## 二、思想详述 让我们开动脑筋想一下:每张订单其实就可以看作是一个区间(操作),左右区间分别为开始时间和结束时间,所以这不就是一个区间操作吗——首选线段tree啦!但是笔者在这里并不打算介绍线段树,因为虽然线段tree操作方便、复杂度低,但是——————我不会写啊qwq!(逃 并且总感觉你考试的时候撸一个线段树模板的时间完全可以多打两个暴力啊qwq(~~虽然暴力也不一定对~~) 所以,选择引入一种好理解、好实现的算法:**差分数组** 在介绍差分之前,需要介绍**前缀和思想** (qwq此处当然只会讲一维线性的前缀和啦) **我们有一组数(个数小于等于一千万),并且有一大堆询问——给定区间l、r,求l、r之间所有数之和(询问个数小于等于一千万) ** 此处暴力肯定不行啊(O(N*Q*length)),那么我们来观察前缀和是怎么做的:用sum[i]来存储前i个数的和,然后用sum[r]-sum[l-1]来表示l~r之间所有数的和。(l-1原因是l~r只看要包含l)而sum数组便可以通过简单的递推求出来 代码核心: ```cpp for(int i=1;i<=n;i++) {cin>>a[i];sum[i]=sum[i-1]+a[i];} for(int i=1;i<=q;i++) {cin>>l>>r;cout<<sum[r]-sum[l-1]<<" ";} ``` 而所谓的差分数组,即是前缀和数组的逆运算: 我们给定前i个数相邻两个数的差(1<=i<=n),求每一项a[i](1<=i<=n)。 此时无非就是用作差的方式求得每一项,此时我们可以有一个作差数组diff,diff[i]用于记录a[i]-a[i-1],然后对于每一项a[i],我们可以递推出来: ```cpp for(int i=1;i<=n;i++) {cin>>diff[i];a[i]=diff[i]+a[i-1];} for(int i=1;i<=n;i++) {cout<<a[i];} ``` 到这儿,我们可以看出来,**前缀和是用元数据求元与元之间的并集关系,而差分则是根据元与元之间的逻辑关系求元数据,是互逆思想**(qwq但是有时元数据和关系数据不是很好辨别或者产生角色反演啊) 但是,理解了前缀和&差分,并不代表肯定能做到模板题:毕竟,**思想只能是辅助工具**啊 ## 三、关于答案二分 一般来说,二分是个很有用的优化途径,因为这样会直接导致减半运算,而对于能否二分,有一个界定标准:**状态的决策过程或者序列是否满足单调性或者可以局部舍弃性。** 而在这个题里,因为如果前一份订单都不满足,那么之后的所有订单都不用继续考虑;而如果后一份订单都满足,那么之前的所有订单一定都可以满足,符合局部舍弃性,所以可以二分订单数量。 ## 四、终于要bb正解了! 首先,要明白如为什么要用区间差分而不是区间前缀和:因为这个题每次操作针对的对象都是原本题目中给的元数据,而不是让求某个关系,所以采用差分。 其次,要知道差分会起到怎样的作用:因为diff数组决定着每个元数据的变化大小、趋势,所以,当我们想要针对区间操作时,钱可以转化成对diff数组操作: ```cpp diff[l[i]]+=d[i]; diff[r[i]+1]-=d[i];//d[i]是指每天要借的教室数 ``` 因为后面的元数据都由之前的diff数组推导出来,所以改变diff[i]就相当于改变[i](包括)之后的每一个值,并通过重新减去改变的量,达到操作区间的目的。 then,我们需要想明白策略:从第一份订单开始枚举,直到无法满足或者全枚举完结束。 最后,一点提示,我下面的标程是通过比大小来判断是否满足,而不是作差判负数————能不出负数就别出负数,否则容易基佬紫(re)/手动滑稽 贴标程: ```cpp #include<iostream> #include<cstring> #include<cstdio> using namespace std; int n,m; int diff[1000011],need[1000011],rest[1000011],r[1000011],l[1000011],d[1000011]; bool isok(int x) { memset(diff,0,sizeof(diff)); for(int i=1;i<=x;i++) { diff[l[i]]+=d[i]; diff[r[i]+1]-=d[i]; } for(int i=1;i<=n;i++) { need[i]=need[i-1]+diff[i]; if(need[i]>rest[i])return 0; } return 1; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&rest[i]); for(int i=1;i<=m;i++)scanf("%d%d%d",&d[i],&l[i],&r[i]); int begin=1,end=m; if(isok(m)){cout<<"0";return 0;} while(begin<end) { int mid=(begin+end)/2; if(isok(mid))begin=mid+1; else end=mid; } cout<<"-1"<<endl<<begin; } ``` writter:皎月半洒花_pks