传送门: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
另外,这确实是一道非常好的题目!