题目链接:https://www.luogu.com.cn/problem/P1083
题目大意:现有n天,每天有 r i r_i ri的空教室可供租借,当前有m个具有先后次序的订单,每个订单的要求是在第 l i l_i li天到第 r i r_i ri天占用 d i d_i di间教室。要求编程,从先到后处理这些订单,倘若到了第 j j j个订单不能被满足,则输出-1换行后,输出该订单号。倘若所有都可以同时满足,则输出0,结束程序。
数据范围如下图所示:
我们从100%的数据可以看出,这题标准思路的时间复杂度应该是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),并且从答案的单调性可以得知应该是二分做法,对此我们暂时跳过,先分析朴素做法。
朴素做法:预期得分 30~40分
我们用模拟的思路,这个思路应该很容易想到。首先用数组 e m p t y _ r o o m [ i ] empty\_room[i] empty_room[i]记录每天的空教室,用 l e f t [ i ] left[i] left[i]记录每个订单的左边界,用 r i g h t [ i ] right[i] right[i]记录每个订单的右边界,最后用 v a l u e [ i ] value[i] value[i]记录每个订单租借的数量。
我们依次处理每个订单,处理的方法是在 e m p t y _ r o o m [ i ] empty\_room[i] empty_room[i]数组上,对于每个订单的左边界到右边界,减去租借教室的数量。倘若处理到第 j j j个订单时,出现了 e m p t y _ r o o m [ i ] empty\_room[i] empty_room[i]数组的第 i i i个元素是负数的情况,就说明第 i i i天教室不够用,此时可以输出答案。
倘若m个订单全部处理完毕,数组上没有负数的情况,说明每天的空教室都够用,此时输出答案0即可。
分析一下预期得分,这个做法最坏情况的时间复杂度是O(m*n),其实还说得过去,30分稳稳拿,对于70%的数据范围靠人品和卡常有概率偷来几个点,不过两个点不能再多了。
二分答案+差分做法:预期得分 100分
从数据范围上看,我们可以从二分的方向去考虑。二分的前提条件是答案具有单调性,那么我们需要先要确定这个前提条件。在处理订单的流程中,我们每一天的空教室数量只能减少,不能增多,这说明处理到某个订单时某一天的教室不够用了,之后再处理订单只会导致教室更加不够用。虽然推理不严谨,但是这个单调性还是可以通过逻辑想清楚的。
因此我们可以确定基本的二分答案思路,确定二分的左边界( b e g i n begin begin)为 1 1 1,右边界( e n d end end)为 m m m, m i d = ( 1 + m ) / 2 mid=(1+m)/2 mid=(1+m)/2,假设答案是 m i d mid mid,计算在该答案下教室是否够用,若够用则做出调整 b e g i n = m i d begin=mid begin=mid,若不够用则做出另一种调整 e n d = m i d end=mid end=mid。这里二分的复杂度是 O ( log n ) O({\log}n) O(logn),现在我们需要考虑如何在 O ( n ) O(n) O(n)的复杂度,完成一次答案检验的操作。
这个时候需要用到差分的技巧,我们就该题进行一个分析:我们一共有m个订单,这m个订单的操作都是对于一个区间进行增减,而当前复杂度希望的是我们仅遍历一次数组的每个元素就可以完成这m个区间的增减。这意味着我们可以用"打标记"的方法来完成这一目的,我们首先设立一个数组 d i f f [ u ] diff[u] diff[u]作为我们的标记数组,区间的左边界 l e f t [ i ] left[i] left[i]我们把它定义为“增标记”,代表一个区间的开始,操作是 d i f f [ l e f t [ u ] ] + v a l u e [ u ] diff[left[u]]+value[u] diff[left[u]]+value[u];区间的右边界 r i g h t [ i ] right[i] right[i],我们把它定义为“减标记”,操作是 d i f f [ r i g h t [ u ] ] − v a l u e [ u ] diff[right[u]]-value[u] diff[right[u]]−value[u]代表的一个区间的结束,并且每个点上的值都是可以叠加的。当我们对空教室数组进行遍历时,这个标记数组就记载了所有单个区间修改操作叠加在一起的总操作。在对 e m p t y _ r o o m [ i ] empty\_room[i] empty_room[i]数组进行遍历时,遍历到某一点 i i i时, e m p t y r o o m [ i − 1 ] + d i f f [ i ] empty_room[i-1]+diff[i] emptyroom[i−1]+diff[i]即为 i i i点剩下的空教室数量,这样我们通过标记数组 d i f f [ u ] diff[u] diff[u],可以完成所有区间更改操作,时间复杂度是 O ( n + m ) O(n+m) O(n+m),也就是 O ( n ) O(n) O(n)。若空教室数组中有元素为负数则到当前订单时不可行,若空教室数组全为正数,则当前订单及之前的订单都可行。(标记数组就是差分数组)
这样我们的总复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn),可行,下面展示代码。
#include<bits/stdc++.h>
using namespace std;
int a[1000011],d[1000011],l[1000011],r[1000011];
long long modify[1000011];
int n,m;
bool pd(int x)
{
memset(modify,0,sizeof(modify));//清空差分数组modify
for(int i=1;i<=x;i++)//将每个区间的更改记录在差分数组上
{
modify[l[i]]+=d[i];
modify[r[i]+1]-=d[i];
}
long long p=0;//为降低时间复杂度,我们不另起一个数组,而是通过p来记录共有x个订单时,
for(int i=1;i<=n;i++)//对于某一天教室的需求量,用需求量与现有量进行对比。
{
p+=modify[i];
if(p>a[i]) return 0;
}
return 1;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=m;i++)
{
cin>>d[i]>>l[i]>>r[i];
}
if(pd(m))
{
cout<<0;
return 0;
}
else cout<<-1<<endl;
int begin=1,end=m;
while(begin<end)
{
int mid=(begin+end)/2;
if(pd(mid))
{
begin=mid+1;
}
else
{
end=mid;
}
}
cout<<begin;
}
有疏漏和讲解不清的地方请私信留言,祝你新的一天RP++。