[NOIP2012] 借教室 题解

题解:

  把题面浓缩一下:给定长度为n的正整数序列,m个区间操作,每次操作是把区间[s,t]内所有的数都减去一个d。求使得序列第一次出现负数的最小操作。

由于序列随着操作的进行时不断减小的,也就是说,若第i次操作使得序列出现负值,那么对大于i的操作也必定会使得序列出现负值。所以,我们可以二分答案,二分操作数。设当前二分的操作数是i,我们就用差分执行前i个操作,若出现负值,说明我们可以将二分右边界缩小,若无负值,就可以将二分左边界扩大。这样的时间复杂度是O(nlogn)。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1000000;
int n,m,ans;

struct point{
    int d,s,t;
}p[N+5];

int r[N+5],b1[N+5],b2[N+5];

int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x;
}

bool check(int x){
    bool flag=false;
    memcpy(b2,b1,sizeof(b1));
    for(int i=1;i<=x;++i){
        b2[p[i].s]-=p[i].d;
        b2[p[i].t+1]+=p[i].d;
    }
    for(int i=1;i<=n;++i){
        b2[i]+=b2[i-1];
        if(b2[i]<0){
            flag=true;
            break;
        }
    }
    return flag;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) r[i]=read();
    for(int i=1;i<=m;++i) p[i].d=read(),p[i].s=read(),p[i].t=read();
    for(int i=1;i<=n;++i) b1[i]=r[i]-r[i-1];
    int l=1,r=n;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            r=mid-1;
        }else l=mid+1;
    }
    if(!ans) printf("0\n");
    else printf("-1\n%d",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Asika3912333/p/11735598.html