1266. [NOIP2012] 借教室

★★☆   输入文件:classrooms.in   输出文件:classrooms.out   简单对比

时间限制:1 s   内存限制:128 MB

【题目描述】

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要 向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。 

面对海量租借教室的信息,我们自然希望编程解决这个问题。 

我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份 订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租 借教室(包括第sj天和第tj天),每天需要租借dj个教室。 

我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提 供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。 

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教 室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申 请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。 

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改 订单。

【输入格式】

第一行包含两个正整数n,m,表示天数和订单的数量。 

第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。 

接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在 第几天。 

每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。

【输出格式】

如果所有订单均可满足,则输出只有一行,包含一个整数 0。否则(订单无法完全满足) 输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。

【样例输入】

4 3 
2 5 4 3 
2 1 3 
3 2 4 
4 2 4 

【样例输出】

-1
2

【输入输出样例说明】

第 1 份订单满足后,4 天剩余的教室数分别为 0,3,2,3。第 2 份订单要求第 2 天到 第 4 天每天提供 3 个教室,而第 3 天剩余的教室数为 2,因此无法满足。分配停止,通知第 2 个申请人修改订单。

【数据范围】

对于 10%的数据,有1 ≤ n,m ≤ 10; 

对于 30%的数据,有1 ≤ n,m ≤ 1000; 

对于 70%的数据,有1 ≤ n,m ≤ 10^5; 

对于 100%的数据,有1 ≤ n,m ≤ 10^6,0 ≤ ri,dj ≤ 10^9,1 ≤ sj ≤ tj ≤ n。

 

维护区间最小值:

//直接线段树区间查询最小值的话是过不了的
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;
const int N = 1e6 + 10;

#define lson jd << 1
#define rson jd << 1 | 1
#define oo 999999999
#define LL long long

struct Node{
    LL l, r, minn, f;
}T[N << 2];
int n, m;
LL ans_min;

inline int read(){
    int x = 0; char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

void down(int jd){
    T[lson].f += T[jd].f;
    T[rson].f += T[jd].f;
    T[lson].minn += T[jd].f;
    T[rson].minn += T[jd].f;
    T[jd].f = 0;
}

void build_tree(int l, int r, int jd){
    T[jd].l = l; T[jd].r = r;
    if(l == r){
        T[jd].minn = read();
        return ;
    }
    int mid = (l + r) >> 1;
    build_tree(l, mid, lson);
    build_tree(mid + 1, r, rson);
    T[jd].minn = min(T[lson].minn, T[rson].minn);
}

void Sec_ask(int l, int r, int jd, int x, int y){
    if(x <= l && r <= y){
        ans_min = min(ans_min, T[jd].minn);
        return ;
    }
    if(T[jd].f) down(jd);
    int mid = (l + r) >> 1;
    if(x <= mid) Sec_ask(l, mid, lson, x, y);
    if(y > mid) Sec_ask(mid + 1, r, rson, x, y);
}

void Sec_g(int l, int r, int jd, int x, int y, int yj){
    if(x <= l && r <= y){
        T[jd].minn += yj;
        T[jd].f += yj;
        return ;
    }
    int mid = (l + r) >> 1;
    if(x <= mid) Sec_g(l, mid, lson, x, y,yj);
    if(y > mid) Sec_g(mid + 1, r, rson, x, y, yj);
    T[jd].minn = min(T[lson].minn, T[rson].minn);
}

int main()
{
    n = read();
    m = read();
    build_tree(1, n, 1);
    for(int i = 1; i <= m; i ++)
    {
        int want = read(), l = read(), r = read();
        ans_min = oo;
        Sec_ask(1, n, 1, l, r);
        if(ans_min < want){
            printf("-1\n%d", i);
            return 0;
        }
        Sec_g(1, n, 1, l, r, -want);
    }
    
    return 0;
}

 

前缀和+二分answer

/*
用某一天的前缀和表示该天需要的教室数 比如一开始数列a是0 0 0 0 0 0
 前缀和0 0 0 0 0 0  3到5天需要2的教室 将a[3]+=2,a[6]-=2 数列变为0 
 0 2 0 0 -2 前缀和变为0 0 2 2 2 0  这样就实现了增加3-5需要的教室数  
 二分订单数量,判断一下前mid个订单是否可以。具体操作是前缀和处理,
 即每读入一个订单就在起始天+要借的房间数量,在结束天的下一天减去要
 借的房间数量。然后比较每一天的前缀和与本天总共的房间数的大
 小,如果房间数<前缀和,就说明前mid个订单有问题,向前二分;否则就向
 后二分。证明:在一个订单的起始天+要借的房间数量,在结束天的
 下一天减去要借的房间数量。设一个数组c[i],记录前缀和。读入的数据是
 d,s,t C[s]:=c[s]+d;c[t+1]:=c[t+1]-d;  那么如果第i天在s和t之间,那么
 前i天的sum{c[i]}中有c[s],相当于已经记下第i天的订单数量了。如果第i天
 在t之后,前i天的sum{c[i]}中有c[s]和c[t],因为c[s]+d+c[t+1]-d=c[s]+c[t],
 所以这个订单只对s和t中间天数起作用。得证! 
*/

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
const int N = 1000010;

int n, m, ans;
int a[N], d[N], x[N], y[N], s[N], sum;

inline int read(){
    int x = 0; char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

inline bool judge(int v){
    memset(s, 0, sizeof(s));
    sum = 0;
    for(int i = 1; i <= v; i ++)
    {
        s[x[i]] += d[i];
        s[y[i]+1] -= d[i];
    }
    for(int i = 1; i <= n; i ++)
    {
        sum += s[i];
        if(sum > a[i]) return 0;
    }
    return 1;
}

int main()
{
    n = read();
    m = read();
    for(int i=1; i<=n; i++) a[i] = read();
    for(int i=1; i<=m; i++) d[i] = read(), x[i] = read(), y[i] = read();
    int l = 1, r = m;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(!judge(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    if(!ans) printf("0");
    else printf("-1\n%d",ans);
    return 0;
}

 

  

posted @ 2017-07-18 17:22  ioioioioioio  阅读(165)  评论(0编辑  收藏  举报