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……