借教室[NOIP2012]解题报告

思路一: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)+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");
}  
思路二:O((n+m)logm)
    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>
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");
}

你可能感兴趣的:(线段树,二分,前缀和)