1. 程式人生 > >借教室

借教室

描述 快速 using fine 解決 數量 print 天數 線段樹

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

}

借教室