1. 程式人生 > >NOIP2012 D2 T2 借教室 線段樹 OR 二分法

NOIP2012 D2 T2 借教室 線段樹 OR 二分法

題目描述:
在大學期間,經常需要租借教室。大到院系舉辦活動,小到學習小組自習討論,都需要向學校申請借教室。教室的大小功能不同,借教室人的身份不同,借教室的手續也不一樣。

面對海量租借教室的資訊,我們自然希望程式設計解決這個問題。

我們需要處理接下來n天的借教室資訊,其中第i天學校有r(i)個教室可供租借。共有m份訂單,每份訂單用三個正整數描述,分別為d(j),s(j),t(j),表示某租借者需要從第s(j)天到第t(j)天租借教室(包括第s(j)天和第t(j)天),每天需要租借d(j)個教室。

我們假定,租借者對教室的大小、地點沒有要求。即對於每份訂單,我們只需要每天提供d(j)個教室,而它們具體是哪些教室,每天是否是相同的教室則不用考慮。

借教室的原則是先到先得,也就是說我們要按照訂單的先後順序依次為每份訂單分配教室。如果在分配的過程中遇到一份訂單無法完全滿足,則需要停止教室的分配,通知當前申 請人修改訂單。這裡的無法滿足指從第s(j)天到第t(j)天中有至少一天剩餘的教室數量不足d(j)個。

現在我們需要知道,是否會有訂單無法完全滿足。如果有,需要通知哪一個申請人修改訂單。

輸入格式
第一行包含兩個正整數n,m,表示天數和訂單的數量。

第二行包含n個正整數,其中第i個數為r(i),表示第i天可用於租借的教室數量。

接下來有m行,每行包含三個正整數d(j),s(j),t(j),表示租借的數量,租借開始、結束分別在第幾天。

每行相鄰的兩個數之間均用一個空格隔開。天數與訂單均用從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 ≤ r(i),d(j) ≤ 10^9,1 ≤ s(j) ≤ t(j) ≤ n。




思路:
1.
線段樹 維護區間的最小值,每次修改可以運用打標記的方法節省時間(關於節省時間 我後面有話說)。。。
2.
二分?(可惜並不會寫,不知道怎麼二分)。
晚上補了個二分的程式。
(若要看正解,請直接翻至頁面底部。。 中間廢話多)

只能寫線段樹了。。/(ㄒoㄒ)/~~
首先在TYVJ上寫了一個不加lazy標記的線段樹

//By SiriusRen
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int tree[1000005],xx,yy,zz,lazy[1000005],n,m;
void build(int l,int r,int pos){
    if(l==r){scanf("%d",&tree[pos]);return;}
    int mid=(l+r)/2;
    build(l,mid,pos*2);build(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
int update(int l,int r,int pos){
    if(l==r)return tree[pos]-=zz;
    int mid=(l+r)/2;
    if(mid<xx)return update(mid+1,r,pos*2+1);
    else if(mid>=yy)return update(l,mid,pos*2);
    else return tree[pos]=min(update(l,mid,pos*2),update(mid+1,r,pos*2+1));
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
int main(){
    scanf("%d%d",&n,&m);
    build(1,n,1);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&zz,&xx,&yy);
        if(update(1,n,1)<0){printf("-1\n%d",i);return 0;}
    }
    puts("0");
    return 0;
}

後果很慘烈。。。
這裡寫圖片描述


改了改 改了很久很久(期間經過無數次崩潰),改成了加Lazy標記的線段樹。

//By SiriusRen
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int tree[10000005],xx,yy,zz,lazy[10000005],n,m;
void build(int l,int r,int pos){
    if(l==r){scanf("%d",&tree[pos]);return;}
    int mid=(l+r)/2;
    build(l,mid,pos*2);build(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
void push_down(int pos){
    tree[pos]-=lazy[pos];
    lazy[pos*2]+=lazy[pos];lazy[pos*2+1]+=lazy[pos];
    lazy[pos]=0;
}
void update(int l,int r,int pos){
    if(lazy[pos])push_down(pos);
    if(l>yy||r<xx)return;
    if(l>=xx&&r<=yy){lazy[pos]+=zz;push_down(pos);return;}
    if(lazy[pos])push_down(pos);
    int mid=(l+r)/2;
    update(l,mid,pos*2);update(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);

}
int main(){
    scanf("%d%d",&n,&m);
    build(1,n,1);
    for(int ii=1;ii<=m;ii++){
        scanf("%d%d%d",&zz,&xx,&yy);update(1,n,1);
        if(tree[1]<0){
            printf("-1\n%d",ii);return 0;}
    }
    puts("0");
    return 0;
}

這裡寫圖片描述
還是T了一個點。。



怎麼辦呢 怎麼辦呢(此時我的內心接近崩潰。。)
隊長過來了,,說:“你加個讀入優化吧 balabala”

//By SiriusRen
#include <cstdio>
using namespace std;
int min(int x,int y){return x<y?x:y;}
int tree[10000005],xx,yy,zz,lazy[10000005],n,m;
void build(int l,int r,int pos){
    if(l==r){scanf("%d",&tree[pos]);return;}
    int mid=(l+r)/2;
    build(l,mid,pos*2);build(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
void push_down(int pos){
    tree[pos]-=lazy[pos];
    lazy[pos*2]+=lazy[pos];lazy[pos*2+1]+=lazy[pos];
    lazy[pos]=0;
}
void update(int l,int r,int pos){
    if(lazy[pos])push_down(pos);
    if(l>yy||r<xx)return;
    if(l>=xx&&r<=yy){lazy[pos]+=zz;push_down(pos);return;}
    if(lazy[pos])push_down(pos);
    int mid=(l+r)/2;
    update(l,mid,pos*2);update(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
int get(){
    int x=0;char p=getchar();
    while(p<'0'||p>'9')p=getchar();
    while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();
    return x;
}
int main(){
    scanf("%d%d",&n,&m);
    build(1,n,1);
    for(int ii=1;ii<=m;ii++){
        zz=get();xx=get();yy=get();update(1,n,1);
        if(tree[1]<0){
            printf("-1\n%d",ii);return 0;}
    }
    puts("0");
    return 0;
}

我就加了一個。 成功水過TYVJ的資料。【鼓掌!】
這裡寫圖片描述
隊長又過來了,說:“Vijos資料很強,你要不試試。。”
當時Vijos已掛,晚上回家就試了試。
這裡寫圖片描述

果斷掛了。【桑心】




各種卡評測機常數。。。。。。

//By SiriusRen
#include <cstdio>
using namespace std;
inline int min(int x,int y){return x<y?x:y;}
int tree[10000005],xx,yy,zz,lazy[10000005],n,m;
void build(int l,int r,int pos){
    if(l==r){scanf("%d",&tree[pos]);return;}
    int mid=(l+r)/2;
    build(l,mid,pos*2);build(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
void update(int l,int r,int pos){
    if(lazy[pos]){
        tree[pos]-=lazy[pos];
        lazy[pos*2]+=lazy[pos];lazy[pos*2+1]+=lazy[pos];
        lazy[pos]=0;
    }
    if(l>yy||r<xx)return;
    if(l>=xx&&r<=yy){lazy[pos]+=zz; tree[pos]-=lazy[pos];
    lazy[pos*2]+=lazy[pos];lazy[pos*2+1]+=lazy[pos];
    lazy[pos]=0;return;}
    if(lazy[pos]){
        tree[pos]-=lazy[pos];
        lazy[pos*2]+=lazy[pos];lazy[pos*2+1]+=lazy[pos];
        lazy[pos]=0;
    }
    int mid=(l+r)/2;
    update(l,mid,pos*2);update(mid+1,r,pos*2+1);
    tree[pos]=min(tree[pos*2],tree[pos*2+1]);
}
inline int get(){
    int x=0;char p=getchar();
    while(p<'0'||p>'9')p=getchar();
    while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();
    return x;
}
int main(){
    register int n=0;register char p=getchar();
    while(p<'0'||p>'9')p=getchar();
    while(p>='0'&&p<='9')n=n*10+p-'0',p=getchar();
    register int m=0;p=getchar();
    while(p<'0'||p>'9')p=getchar();
    while(p>='0'&&p<='9')m=m*10+p-'0',p=getchar();
    build(1,n,1);
    for(int ii=1;ii<=m;ii++){
        xx=yy=zz=0;
        p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')zz=zz*10+p-'0',p=getchar();
        p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')xx=xx*10+p-'0',p=getchar();
        p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')yy=yy*10+p-'0',p=getchar();
        update(1,n,1);
        if(tree[1]<0){printf("-1\n%d",ii);return 0;}
    }
    puts("0");
}

把get函式手動內建了一下。。。
這裡寫圖片描述
哈哈AC了



網上找了個別的題庫交了交。
這裡:http://syzoj.com/problem/14(資料真心良心)
這裡寫圖片描述
就光榮的掛了…….掛了……掛了。。。!!!
然後呢,我就各種改內建,一通亂搞。

//By SiriusRen
#include <cstdio>
using namespace std;
int tree[10000005],xx,yy,zz,lazy[10000005];
inline void build(int l,int r,int pos){
    if(l==r){
        register char p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')tree[pos]=tree[pos]*10+p-'0',p=getchar();
        return;
    }
    register int mid=(l+r)>>1,lson=pos<<1,rson=pos+pos+1;
    build(l,mid,lson);
    build(mid+1,r,rson);
    tree[pos]=tree[lson]<tree[rson]?tree[lson]:tree[rson];
}
inline void update(int l,int r,int pos){
    register int lson=pos<<1,rson=pos+pos+1;
    if(lazy[pos]){
        tree[pos]-=lazy[pos];
        lazy[lson]+=lazy[pos];
        lazy[rson]+=lazy[pos];
        lazy[pos]=0;
    }
    if(l>yy||r<xx)return;
    if(l>=xx&&r<=yy){
        lazy[pos]+=zz;
        tree[pos]-=lazy[pos];
        lazy[lson]+=lazy[pos];
        lazy[rson]+=lazy[pos];
        lazy[pos]=0;
        return;
    }
    if(lazy[pos]){
        tree[pos]-=lazy[pos];
        lazy[lson]+=lazy[pos];
        lazy[rson]+=lazy[pos];
        lazy[pos]=0;
    }
    register int mid=(l+r)>>1;
    update(l,mid,lson);
    update(mid+1,r,rson);
    tree[pos]=tree[lson]<tree[rson]?tree[lson]:tree[rson];
}
int main(){
    register int n=0,m=0;
    register char p=getchar();
    while(p<'0'||p>'9')p=getchar();
    while(p>='0'&&p<='9')n=n*10+p-'0',p=getchar();
    p=getchar();
    while(p<'0'||p>'9')p=getchar();
    while(p>='0'&&p<='9')m=m*10+p-'0',p=getchar();
    build(1,n,1);
    for(int ii=1;ii<=m;ii++){
        xx=yy=zz=0;
        p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')zz=zz*10+p-'0',p=getchar();
        p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')xx=xx*10+p-'0',p=getchar();
        p=getchar();
        while(p<'0'||p>'9')p=getchar();
        while(p>='0'&&p<='9')yy=yy*10+p-'0',p=getchar();
        update(1,n,1);
        if(tree[1]<0){printf("-1\n%d",ii);return 0;}
    }
    puts("0");
}

這裡寫圖片描述
卡時卡過去了哈哈哈哈

總結了幾個小技巧:
1.
讀入優化(粘個函式):

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

2.
少呼叫函式,節省函式呼叫的時間。必要時可以手動內建(Code Length什麼的在AC面前都是浮雲),手寫min函式、max函式什麼的,可以快很多。
3.
關於乘除法
多用位移操作,二進位制的與、或什麼的。(例: %8 就可以寫成 &7,/2就可以寫成>>1 之類的吧。。)
4.
inline和register ? 這種東西不知道靠不靠譜。。
5.
避免多次操作。
比如線段樹中要多次用到pos*2、pos*2+1這種東西。怎麼辦呢? 我們可以開個變數儲存它。用的時候就沒有必要再重複計算了。
6.
最重要的一點吧。。 知道正解最好。。。。。。。。

附正解的二分演算法:
二分前多少個訂單可以滿足,然後判斷。
建立一個數組是a[i]=第i天需要的教室數-第i-1天需要的教室數
這樣第i天需要的教室數就是a[]的字首和。
每個訂單a~b增加c就是a[a]+=c,a[b+1]-=c,把訂單資訊確定後之後掃一遍進行判斷。

// by SiriusRen
#include <cstdio>
#include <cstring>
#define N 1000005
using namespace std;
int l=1,r,n,answer,mid,m,a[N],b[N],d[N],s[N],t[N];
bool solve(){
    int ans=0;memset(b,0,sizeof(b));
    for(int i=1;i<=mid;i++)b[s[i]]+=d[i],b[t[i]+1]-=d[i];
    for(int i=1;i<=n;i++){
        ans+=b[i];
        if(a[i]<ans)return false;
    }
    return true;
}
int main(){
    scanf("%d%d",&n,&m);r=m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=r;i++)scanf("%d%d%d",&d[i],&s[i],&t[i]);
    while(l<=r){
        mid=(l+r)/2;
        if(solve())answer=mid,l=mid+1;
        else r=mid-1;
    }
    if(answer!=m)printf("-1\n%d\n",answer+1);
    else puts("0");
}

寫完線段樹還是覺得二分so easy……