借教室
--srzer
借教室(Borrow Classrooms)
【Problem description】
在大學期間,經常需要租借教室。大到院系舉辦活動,小到學習小組自習討論,都需要向學校申請借教室。教室的大小功能不同,借教室人的身份不同,借教室的手續也不一樣。
面對海量租借教室的信息,我們自然希望編程解決這個問題。
我們需要處理接下來n天的借教室信息,其中第i天學校有ri個教室可供租借。共有m份訂單,每份訂單用三個正整數描述,分別為dj,sj,tj,表示某租借者需要從第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj個教室。
我們假定,租借者對教室的大小、地點沒有要求。即對於每份訂單,我們只需要每天提供dj個教室,而它們具體是哪些教室,每天是否是相同的教室則不用考慮。
借教室的原則是先到先得,也就是說我們要按照訂單的先後順序依次為每份訂單分配教室。如果在分配的過程中遇到一份訂單無法完全滿足,則需要停止教室的分配,通知當前申請人修改訂單。這裏的無法滿足指從第sj天到第tj天中有至少一天剩余的教室數量不足dj個。
現在我們需要知道,是否會有訂單無法完全滿足。如果有,需要通知哪一個申請人修改訂單。
【Input format】
第一行包含兩個正整數n,m,表示天數和訂單的數量。
第二行包含n個正整數,其中第i個數為ri,表示第i天可用於租借的教室數量。
接下來有m行,每行包含三個正整數dj,sj,tj,表示租借的數量,租借開始、結束分別在第幾天。
每行相鄰的兩個數之間均用一個空格隔開。天數與訂單均用從1開始的整數編號。
【Output format】
如果所有訂單均可滿足,則輸出只有一行,包含一個整數0。否則(訂單無法完全滿足)輸出兩行,第一行輸出一個負整數-1,第二行輸出需要修改訂單的申請人編號。
【Algorithm design】
線段樹標記永久化/分治
【Problem analysis】
線段樹標記永久化AC100
對於一個數組中的一連串數字進行修改,還要快速的看出有沒有小於0的節點,自然想到最小區間線段樹。維護一個線段樹,根是葉子的最小值,根節點小於0就輸出答案,這道題就被解決了。
雖然是O(log2n m),數據量最大(10^9),在加上快讀優化後TLE95
在看了許多線段樹題解後終於發現了這個神奇的優化:線段樹標記永久化!
線段樹中的構建,刪除,實在是很難優化了,所以突破點在懶惰標記上,每次懶惰標記都下傳占了一些時間。如果不更改樹節點,直接在標記上永久修改,那麽在下層節點修改之後,上層節點只要:s(sub) = min ( s(lkid) - add(lkid) , s(rkid) - add(rkid) )就可以將狀態傳遞到上層;在上層節點被修改之後,下層節點無需變動,對於下層的變動傳遞到上層時,自然會被加上標記值
這個優化省了不少時間,但我覺得只能適用於無查找操作的線段樹問題,因為只有根節點是真實值。
【Source code】
線段樹標記永久化AC100
#include <bits/stdc++.h>
using namespace std;
struct node
{
int l,r,s,add;
#define l(i) seg_tree[i].l
#define r(i) seg_tree[i].r
#define s(i) seg_tree[i].s
#define add(i) seg_tree[i].add
#define lkid sub<<1
#define rkid sub<<1|1
}seg_tree[4000010];
int n,m,a[1000010];
int fastget()
{
int res=0;
char ch=getchar();
while(ch>‘9‘||ch<‘0‘)
ch=getchar();
while(ch<=‘9‘&&ch>=‘0‘)
{
res=(res<<1)+(res<<3)+ch-‘0‘;
ch=getchar();
}
return res;
}//快讀函數
void build_tree(int sub,int l,int r)
{
l(sub)=l,r(sub)=r,add(sub)=0;
if(l==r)
{
s(sub)=a[l];
return ;
}
int mid=(l+r)>>1;
build_tree(lkid,l,mid);
build_tree(rkid,mid+1,r);
s(sub)=min(s(lkid),s(rkid));
return ;
}//建最小區間線段樹
void cut_tree(int sub,int l,int r,int d)
{
if(l(sub)>r||r(sub)<l)
return ;
if(l(sub)>=l&&r(sub)<=r)
{
add(sub)+=d;
return ;
}
cut_tree(lkid,l,r,d);
cut_tree(rkid,l,r,d);
s(sub)=min(s(lkid)-add(lkid),s(rkid)-add(rkid));//標記永久化
return ;
}
int main()
{
freopen("classroom.in","r",stdin);
freopen("classroom.out","w",stdout);
n=fastget(),m=fastget();
for(int i=1;i<=n;i++)
a[i]=fastget();
build_tree(1,1,n);
for(int i=1;i<=m;i++)
{
int d,b,e;
d=fastget();
b=fastget();
e=fastget();
cut_tree(1,b,e,d);
if(s(1)-add(1)<0)//在永久化後,根節點也要算上標記
{
printf("-1\n%d\n",i);
return 0;
}
}
printf("0\n");
return 0;
}
借教室