1. 程式人生 > >Poj 3667(線段樹,查詢連續區間,區間合併)(經典)

Poj 3667(線段樹,查詢連續區間,區間合併)(經典)

problem

The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a vacation on the sunny shores of Lake Superior. Bessie, ever the competent travel agent, has named the Bullmoose Hotel on famed Cumberland Street as their vacation residence. This immense hotel has N (1 ≤ N ≤ 50,000) rooms all located on the same side of an extremely long hallway (all the better to see the lake, of course).

The cows and other visitors arrive in groups of size Di (1 ≤ Di ≤ N) and approach the front desk to check in. Each group i requests a set of Di contiguous rooms from Canmuu, the moose staffing the counter. He assigns them some set of consecutive room numbers r..r+Di-1 if they are available or, if no contiguous set of rooms is available, politely suggests alternate lodging. Canmuu always chooses the value of r to be the smallest possible.

Visitors also depart the hotel from groups of contiguous rooms. Checkout i has the parameters Xi and Di which specify the vacating of rooms Xi ..Xi +Di-1 (1 ≤ Xi ≤ N-Di+1). Some (or all) of those rooms might be empty before the checkout.

Your job is to assist Canmuu by processing M (1 ≤ M < 50,000) checkin/checkout requests. The hotel is initially unoccupied.

Input

  • Line 1: Two space-separated integers: N and M
  • Lines 2..M+1: Line i+1 contains request expressed as one of two possible formats: (a) Two space separated integers representing a check-in request: 1 and Di (b) Three space-separated integers representing a check-out: 2, Xi, and Di

Output

  • Lines 1…..: For each check-in request, output a single line with a single integer r, the first room in the contiguous sequence of rooms to be occupied. If the request cannot be satisfied, output 0.

Sample Input

10 6
1 3
1 3
1 3
1 3
2 5 5
1 6

Sample Output

1
4
7
0
5

思路

簡化題意即為:

兩種操作
1.找是否存在指定長度的空的(未標記的)區間,如果有,輸出最左端的點,並將這個區間標記
2.從一指定點開始,消除一段指定長度的區間的標記

看到這題很容易想到線段樹,其常用來維護區間的資訊。
其中操作2消除標記就是最基本的區間覆蓋
關鍵問題是操作1怎麼處理
如果只是讓判斷是否存這樣連續未標記的區間,那我只要再維護一個數組,比如slen,它表示當前結點最大連續未標記長度。在pushup和pushdown時維護一下即可。
令人頭疼的是,它不僅要判斷是否存在足夠長度未標記的區間,還要找出最左端的位置

這時候我沒想到怎麼做,看了一下別人的思想,頓有醍醐灌頂之感

維護三個資訊,分別是:
lsum 當前區間從左端點開始向又看,最大連續未標記區間是多長
rsum 當前區間從右端點開始向左看,最大連續未標記區間是多長
msum 遞迴定義:max{ 左半區間msum , 右半區間msum , 左半區間的rsum+右半區間lsum(這兩部分正好拼在一起)}
比如區間6~10
標記是10011 那lsum=0 rsum=0 msum=2 ;
標記是00111 那lsum=2 rsum=0 msum=2 ;

這樣定義的msum即為當前結點表示區間內的最大未標記連續區間


對於lsum和rsum的維護,核心程式碼為:

lsum[rt]=lsum[rt<<1];//當前區間左大連續自然等於左兒子左大連續
rsum[rt]=rsum[rt<<1|1];//當前區間右大連續自然等於右兒子右大連續
//但這樣是極限嗎?顯然不是 如果左兒子全未標記 可能右兒子的左部分也未標記 就有了下面的程式碼
if(lsum[rt]==len-(len>>1)) lsum[rt]+=lsum[rt<<1|1];//if條件是:左大連續等於左區間長 加上右兒子左大連續
if(rsum[rt]==(len>>1)) rsum[rt]+=rsum[rt<<1];//同理

程式碼示例

#include<iostream>
#include<cstdio>
#include<algorithm>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define bug cout<<"test"<<endl
using namespace std;
const int maxn=50100;

int lsum[maxn<<2],rsum[maxn<<2],msum[maxn<<2];
int cov[maxn<<2];//-1 0 1三種狀態

void PushDown(int rt,int len){
    if(cov[rt]!=-1){
        cov[rt<<1]=cov[rt<<1|1]=cov[rt];
        msum[rt<<1]=lsum[rt<<1]=rsum[rt<<1]=cov[rt]?0:len-(len>>1);
        msum[rt<<1|1]=lsum[rt<<1|1]=rsum[rt<<1|1]=cov[rt]?0:len>>1;
        cov[rt]=-1;
    }
}

void PushUp(int rt,int len){
    lsum[rt]=lsum[rt<<1];
    rsum[rt]=rsum[rt<<1|1];
    if(lsum[rt]==len-(len>>1)) lsum[rt]+=lsum[rt<<1|1];
    if(rsum[rt]==(len>>1)) rsum[rt]+=rsum[rt<<1];
    msum[rt]=max(max(msum[rt<<1],msum[rt<<1|1]),rsum[rt<<1]+lsum[rt<<1|1]);
}

void build(int l,int r,int rt){
    msum[rt]=lsum[rt]=rsum[rt]=r-l+1;//一開始均為區間長度
    cov[rt]=-1;
    if(l==r) return ;
    int m=(l+r)>>1;
    build(lson);
    build(rson);
}

void update(int L,int R,int c,int l,int r,int rt){
    if(L<=l&&r<=R){
        msum[rt]=lsum[rt]=rsum[rt]=c?0:r-l+1;
        cov[rt]=c;
        return ;
    }
    PushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(L<=m) update(L,R,c,lson);
    if(m<R) update(L,R,c,rson);
    PushUp(rt,r-l+1);
}

int query(int w,int l,int r,int rt){
    if(l==r) return l;
    PushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(msum[rt<<1]>=w) return query(w,lson);//先考慮左兒子
    else if(rsum[rt<<1]+lsum[rt<<1|1]>=w) return m-rsum[rt<<1]+1;//再考慮兩部分合並的中間部分
    return query(w,rson);//最後考慮右兒子
}

int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    build(1,n,1);
    while(m--){
        int op,a,b;
        scanf("%d",&op);
        if(op==1){
            scanf("%d",&a);
            if(msum[1]<a) puts("0");//最大空出長度都不滿足
            else{
                int p=query(a,1,n,1);
                printf("%d\n",p);
                update(p,p+a-1,1,1,n,1);
            }
        }

        else{
            scanf("%d %d",&a,&b);
            update(a,a+b-1,0,1,n,1);
        }
    }
    return 0;
}

相關推薦

Poj 3667(線段查詢連續區間區間合併)經典

problem The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a vacation on the sunny shore

POJ-3667 線段區間合並入門題

\n 向上 urn 開始 連續 每次 線段樹 最大連續 () 題意:長度為n的區間,m個操作,一開始都是0 1 x表示求出長度為x的0的連續區間的最左端,並把這個區間變成1 2 x y表示將區間[x,y]變成0 線段樹的區間合並第一題: 每次維護左端連續區間長

線段+掃描線【p1884】[Usaco12FEB]過度種植Overplanting …

Description 在一個笛卡爾平面座標系裡(則X軸向右是正方向,Y軸向上是正方向),有\(N(1<=N<=1000)\)個矩形,第i個矩形的左上角座標是\((x1, y1)\),右下角座標是\((x2,y2)\)。問這\(N\)個矩形所覆蓋的面積是多少?注意:被重複覆蓋的區域的面積

Frequent values POJ - 3368(線段區間合併)

Frequent values POJ - 3368 題目連結 題意:一個非遞減序列,隨機詢問區間[l, r]中出現次數最多的數的出現次數; 思路:多次詢問,首先就要想一下線段樹;由題意可知數列中的數是連續的,既然是連續的就有合併的希望!!!那麼就來一發線段樹吧(RMQ也可以做

C - A Simple Problem with Integers POJ - 3468 線段模版區間查詢區間修改

線段樹模版 amp else 計算 更新 namespace scanf spa ger 參考qsc大佬的視頻 太強惹 先膜一下 視頻在b站 直接搜線段樹即可 1 #include<cstdio> 2 using namespace std; 3 con

POJ 2823 線段區間查詢

Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 49775 Accepted: 14360 Case Time Limit: 5000MS Descripti

Tunnel Warfare HDU - 1540線段最長連續區間

ont div n) pan Go build lse ons #define 題意: 一條線上的點,D x是破壞這個點,Q x是表示查詢以x所在的最長的連續的點的個數,R是恢復上一次破壞的點。 解析: 線段樹結點 設置一個 lq記錄區間左端點開始的最大連續個數,

POJ 3264 線段區間最大最小值

很裸的線段樹,沒有什麼好說的,我把根節點所擁有的左右區間都寫在結構體裡面,這樣傳參的時候比較方便。 POJ不支援萬能頭很不習慣。 #include<iostream> #include<cstdio> using namespace std; const int

POJ 3468 線段區間更新

題意: 輸入 n, m表初始有 n 個數, 接下來 m 行輸入, Q x y 表示詢問區間 [x, y]的和;          C x y z 表示區間 [x, y] 內所有數加上 z ; AC_code: #include<iostream> #

HDU 5245 Joyful線段查詢區間和及修改區間每個數為數的平方

題目連結: HDU 5245 Joyful 題意: 查詢區間和及修改查詢區間,將每個數都變為數的平方。模數是9223372034707292160。 分析: 任何一個數的若干次平方後模上9223372034707292160的值都會不變?!你敢信?

線段學習筆記(單點更新+區間查詢最大值+lazy標記+pushdown操作+區間更新+求區間和)

目錄 什麼是線段樹? 線段樹基本操作: 建立線段樹 線段樹單點更新 區間查詢最大最小值 延遲標記(懶人標記)+pushdown操作 區間更新 求區間和 注:以下所有程式碼都是針對維護區間和的。 什麼是線段樹? 線段樹是一種二叉搜尋樹,與區間

Permutation UVA - 11525值域狀數組狀數組區間第k大離線log方log

一次 跳過 += 數字 div ret num while printf Permutation UVA - 11525 看康托展開 題目給出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展開(將n個數的所有排列按字典序

poj 2528 線段+特殊離散化

cto 1-1 there sync ali after hat tor memory Mayor‘s posters Time Limit: 1000MS Memory Limit: 65536K Total Submissions:

「模板」線段靜態開點(單點+區間修改)、動態開點

條件判斷 else detail algo query std 判斷 hup cout 相關講解資料: 樹狀數組:https://blog.csdn.net/qq_34374664/article/details/52787481 (線段樹預備) 線段樹講解:

Buy Tickets POJ - 2828線段求插隊

題意就是給你n個人,每個人有自己的要插入的pos和val,問你最後的排序 思路:逆序插入,最後一個人的位置一定是固定的,因為必須連續插入,所以第i個人插入的時候必須保證前面有pos[i]個空位, 那麼用線段樹記錄位置個數,如果位置不夠,就往後挪。 #include<iostream>

Apple Tree POJ - 3321(線段+dfs序)

Apple Tree POJ - 3321 題目連線 題意:一棵蘋果樹,初始每個節點上都有一個蘋果,共n個節點,m個操作,分兩種: Q x:x節點的子樹中共有多少個蘋果; C x:若x節點處有蘋果,就摘掉,反之就長出蘋果; 思路:根據樹的dfs序將樹轉換線性問題,再利用線段

POJ 3468 線段

  要求:有兩個操作,Q是詢問一段區間的和,C是將一段區間的所有數加上一個數。 方法:線段樹模板題。 1.定義addv[i]為第i個結點對應的區間加上addv[i],但是先不將addv[i]下放給子孫,這樣可以減小時間複雜度。 2.定於sumv[i]為第i個結點對應的區間的

POJ 3277 線段+離散化

題意: 給定n個線段以及沒個線段上的高度,求最終所有線段的矩形面積並。 分析: 由於資料範圍較大,所以採用離散化,再用每個點進行建樹,之前沒這麼建過線段樹,學到了。 #include<cstdio> #include<algorithm> #i

POJ 2528 線段 離散化 逆向思維

要求:向長度為1e8的牆上貼最多1e4個長度不等的海報,每個海報貼在牆上任意位置,問最後能見到幾個海報(見到海報的一部分也算是見到該海報)。 方法:線段樹區間修改 離散化 逆向思維 1.建造一個1e8的線段樹必定TLE,因此需要離散化(壓縮區間)。 此題離散化具體步驟:先用ql陣列和qr

2018年9月3日 貝殼筆試題線段+二分查詢

題目: 演算法思路: 考慮每一個骨牌:倒下後產生區間[xi,xi+hi−1][xi,xi+hi−1]。 區間內會連帶後面未知長度區間的骨牌倒下。假設最後倒下的區間為[xi,farthesti][xi,farthesti]。 首先很容易寫出時間複雜度為O