题目
思路
二分答案
首先可以看出,最后答案ans存在明显的单调性,这个单调性是1或0的单调性,有点特殊,大概是这样的:
所以就可以二分答案,二分出来的答案应该满足这样的规律:对于ans个订单,教室够借。对于ans+1个订单,教室不够借。
这里给出二分的模板:(来自WYF)
所有的二分都应该是下面两种的一个,不能出现类似于这样的二分:
mid = (l+r) >> 1
if(...) return mid;
if(...) l = mid+1;
else r = mid-1;
另外应该注意的是,对于模板的第二种情况(本题就是),应该mid=(l+r+1) >> 1而不是直接mid = (l+r) >> 1,原因是比如l = 1, r = 2, calc(1) = true的情况就会进入死循环。
clac(i)的计算
1.考虑暴力,对于m个区间,每个区间对区间内的每个元素加d[i]:
复杂度是:
O(n∗m)
O
(
n
∗
m
)
2.差分:
像本题这样,有一个序列a,不断对区间加同一个数,最后求出数组每个位置的值,这种特殊的问题,可以用差分来解决。差分就相当于是前缀和的逆运算,本题只需将序列a先差分,然后进行差分的“区间加同一个数操作”,最后再前缀和回来即可。
代码是这样的:
// 由于本题序列初始都为0,所以不用先差分
_rep(i,1,mid){ // 差分的“区间加同一个数操作”
cf[s[i]] += d[i];
cf[t[i]+1] -= d[i];
}
_rep(i,1,n) sum[i] = sum[i-1]+cf[i]; // 求一次前缀和,就回来了
这里假设有区间[s,t],需要给区间内每个数加d。那么只需给差分好的数组cf,cf[s]+=d,cf[t+1]-=d即可。(注意这里是t+1不是t)
复杂度:
O(n+m)
O
(
n
+
m
)
所以本题总的复杂度就是,
O(logn∗(n+m))=O(nlogn)
O
(
l
o
g
n
∗
(
n
+
m
)
)
=
O
(
n
l
o
g
n
)
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = a; i<b; i++)
#define _rep(i,a,b) for(int i = a; i<=b; i++)
#define ll long long
using namespace std;
const int maxn = 1000000+100;
int n, m;
ll r[maxn], d[maxn], s[maxn], t[maxn];
ll cf[maxn], sum[maxn];
bool calc(int mid){
memset(cf,0,sizeof(cf));
memset(sum,0,sizeof(sum));
_rep(i,1,mid){
cf[s[i]] += d[i];
cf[t[i]+1] -= d[i];
}
_rep(i,1,n) sum[i] = sum[i-1]+cf[i];
bool canbe = true;
_rep(i,1,n) if(sum[i] > r[i]) canbe = false;
return canbe;
}
int main(){
scanf("%d%d",&n,&m);
_rep(i,1,n) scanf("%lld",&r[i]);
ll dd,ss,tt;
_rep(i,1,m){
scanf("%lld%lld%lld",&dd,&ss,&tt);
d[i] = dd; s[i] = ss; t[i] = tt;
}
int L = 0, R = m;
while(L<R){
int mid = (L+R+1)>>1; // 注意此处+1的特殊情况
if (calc(mid)) L = mid;
else R = mid-1;
}
if (L == m) printf("0\n");
else printf("-1\n%d\n",L+1);
return 0;
}
//
还有一个更简单的差分代码实现,但不是很直观:
(只用了一个cf数组)
bool calc(int mid){
memset(cf,0,sizeof(cf));
_rep(i,1,mid){
cf[s[i]] += d[i];
cf[t[i]+1] -= d[i];
}
_rep(i,1,n) cf[i] = cf[i-1]+cf[i];
bool canbe = true;
_rep(i,1,n) if(cf[i] > r[i]) canbe = false;
return canbe;
}