思路一:O(mlogn)
裸的线段树,维护最小值和区间修改;由于是第一次写线段树,所以不太会写。
代码:
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
char * ptr=new char[50000000];
int tree[2500000],lazy[2500000],a[1000001],i;
inline void build(int node,int l,int r){
if(l==r)tree[node]=a[l];
else{
build(node<<1,l,(l+r)>>1);
build((node<<1)+1,((l+r)>>1)+1,r);
tree[node]=min(tree[node<<1],tree[(node<<1)+1]);
}
}
inline void sub(int node,int l,int r,int a,int b,int s){
if(
r<=b
&&
l>=a
){
lazy[node]+=s;
if(tree[node]<lazy[node]){
printf("-1\n%d",i+1);
exit(0);
}
return;
}
if(tree[node]<lazy[node]){
printf("-1\n%d",i+1);
exit(0);
}
if (
r < a
||
l > b
) return ;
lazy[node<<1]+=lazy[node];
lazy[node<<1]+=lazy[node];
lazy[(node<<1)+1]+=lazy[node];
lazy[node]=0;
sub(node<<1,l,(l+r)>>1,a,b,s);
sub((node<<1)+1,((l+r)>>1)+1,r,a,b,s);
tree[node]=min(tree[node<<1]-lazy[node<<1],tree[(node<<1)+1]-lazy[(node<<1)+1]);
}
inline void in(int &x){
while(*ptr<'0'||*ptr>'9')++ptr;
x=0;
while(*ptr>47&&*ptr<58)x=x*10+*ptr++-'0';
}
int main(){
int n,m,d,s,t;
freopen("classrooms.in","r",stdin);freopen("classrooms.out","w",stdout);
fread(ptr,1,50000000,stdin);
in(n),in(m);
for(i=1,++n;i<n;++i)in(a[i]);
--n;
build(1,1,n);
for(i=0;i<m;++i){
in(d),in(s),in(t);
sub(1,1,n,s,t,d);
}
printf("0");
}
bia题解,与上一种思路相比,主要优点是常数小。
差分序列:(可用于区间增减)记录相邻两个量的变化量,所以当在一段区间[l,r]上增加a时,只需要在l处加a,在r+1处-a即可;
单调性:(可二分的充要条件)易知若前t个不合法,则前k个必不合法,k∈[t,m].
所以,我们可以利用差分序列在O((n+m)logm)的时间复杂度内二分。
由于是题解,而且还不是最优解,所以我就没写这个。
思路三:O(n+m)
bia QWERTler大神!
我们可以在线的做思路二的过程,实际上,这就是很多二分的题转化成线性时间的方法。
我们可以先把所有的区间加到差分序列中,然后从前往后扫一遍n;如果我们发现当前的时间已经被减成负的了的话,就恢复最后一个区间,直到时间非负为止。
那么为什么我当扫到i的时候扫1~top的区间,而在<i的时候扫的是1~>top的区间呢?这样不会出问题么?
这就是很多二分都可以转化成线性时间的方法,这正是因为其单调性,若在相同若干天使用之前1~>top的区间都不会出现负时间,那么使用1~top的区间必然不会出现负时间。
于是,我们得到了O(n+m)的算法。
代码能力总结:
写题的过程不小心把top写成i了,结果查了1个小时才查出来。以后写题一定要集中注意力,不求快,但求准。
代码:
#include<iostream>
差分序列:(可用于区间增减)记录相邻两个量的变化量,所以当在一段区间[l,r]上增加a时,只需要在l处加a,在r+1处-a即可;
单调性:(可二分的充要条件)易知若前t个不合法,则前k个必不合法,k∈[t,m].
所以,我们可以利用差分序列在O((n+m)logm)的时间复杂度内二分。
由于是题解,而且还不是最优解,所以我就没写这个。
思路三:O(n+m)
bia QWERTler大神!
我们可以在线的做思路二的过程,实际上,这就是很多二分的题转化成线性时间的方法。
我们可以先把所有的区间加到差分序列中,然后从前往后扫一遍n;如果我们发现当前的时间已经被减成负的了的话,就恢复最后一个区间,直到时间非负为止。
那么为什么我当扫到i的时候扫1~top的区间,而在<i的时候扫的是1~>top的区间呢?这样不会出问题么?
这就是很多二分都可以转化成线性时间的方法,这正是因为其单调性,若在相同若干天使用之前1~>top的区间都不会出现负时间,那么使用1~top的区间必然不会出现负时间。
于是,我们得到了O(n+m)的算法。
代码能力总结:
写题的过程不小心把top写成i了,结果查了1个小时才查出来。以后写题一定要集中注意力,不求快,但求准。
代码:
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<cstdlib>
char * ptr=new char[50000000];
int s[1000001],l[1000001],r[1000001],d[1000001];
inline void in(int &x){
while(!isdigit(*ptr))++ptr;
x=0;
while(isdigit(*ptr))x=x*10+*ptr++-'0';
}
int main(){
int n,m,now,pred,i,top;
freopen("classrooms.in","r",stdin);freopen("classrooms.out","w",stdout);
fread(ptr,1,50000000,stdin);
in(n),in(m);
pred=0;
for(i=1,++n;i<n;++i){
in(now);
s[i]=now-pred;
pred=now;
}
for(i=1,++m;i<m;++i){
in(d[i]),in(l[i]),in(r[i]);
s[l[i]]-=d[i];
s[++r[i]]+=d[i];
}
now=0,top=--m;
for(i=1;i<n;++i){
now+=s[i];
while(now<0){
if(r[top]>i){
if(l[top]<=i)now+=d[top];
else s[l[top]]+=d[top];
s[r[top]]-=d[top];
}
--top;
}
}
if(top<m)printf("-1\n%d",top+1);
else printf("0\n");
}