1. 程式人生 > 實用技巧 >Luogu P2894 [USACO08FEB]Hotel G

Luogu P2894 [USACO08FEB]Hotel G

思路

這道題其實說白了也就是一個維護最大連續長度為0的子串,再根據土木中的要求進行更新和求值即可。

我寫到這就不知道該怎麼說了,所以下面說的可能有些亂。

這道題維護的區間最大子段和GSS1和GSS3的區間最大子段和的維護方法相當的類似,但是由於這道題讓你維護的是區間最長的連續為0的子串,而且在查詢的時候如果滿足了要求,輸出的是左端點的序號。

為了維護區間最長連續為0的子串長度,和GSS1、GSS3類似,我們也是要維護區間最大字首、區間最大字尾和(這裡的字首和和字尾和並不是傳統意義上的,維護的東西和區間最長連續空房間是

差不多的),不同的是我們不用維護區間和了,而是要維護區間長度,這也是因為此題維護的是最大區間為0的子串長度。那究竟應該怎麼維護呢?

對於區間最長連續為0的子串,它的分佈仍然是有3種情況:

1.全部在左兒子

2.全部在右兒子

3.在左兒子和右兒子中都有

我們在pushdown的時候,同樣也是對這3種情況進行討論,最後合併取最大值即可。

對於區間最長字首連續空房間,有兩種情況:

1.全部在左兒子中

2.在左兒子和右兒子中都有:

同樣是在pushdown的時候維護,但是並不是取最大值(因為要連續),而是判斷一下左兒子的最大連續空房間數量是不是和左兒子的區長度相同,若相同,那麼就是第2種情況,否則就是第一種情況。

對於區間最長字尾連續空房間,其實就是區間最大字首空房間映象翻轉時候的樣子,維護方法極其類似,所以在這裡就不再贅述,請大家感性理解一下(因為本人時間有限,沒法寫的那麼詳細)。

寫到這的時候我感覺有點詞窮(我太菜了,不知道該怎麼說了),所以就直接看程式碼好了,主要的東西都寫在程式碼註釋裡了。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define MAXN 50010
int n, m;
struct node{
    int len;//區間長度
    int val_f, val_b;//最大字首和最大字尾
    int val_mal;//最長連續空房間
    int tag;//lazytag,0表示還沒有操作過,1表示將退房,2表示住上人
} tree[MAXN << 2];
inline int lson(int k) { return k << 1; }//求左兒子
inline int rson(int k) { return k << 1 | 1; }//求右兒子
inline void push_up(int k){
    if(tree[lson(k)].val_mal==tree[lson(k)].len)//若左區間全部為空房,那麼就是左區間長度+右區間最大字首
        tree[k].val_f = tree[lson(k)].len + tree[rson(k)].val_f;
    else//否則直接儲存左兒子的最大字首
        tree[k].val_f = tree[lson(k)].val_f;
    if(tree[rson(k)].len==tree[rson(k)].val_mal)//若右區間全部為空房,那麼就是右區間長度+左區間最大字首
        tree[k].val_b = tree[rson(k)].len + tree[lson(k)].val_b;
    else//否則直接儲存右兒子最大字尾
        tree[k].val_b = tree[rson(k)].val_b;
    tree[k].val_mal = std::max(std::max(tree[lson(k)].val_mal, tree[rson(k)].val_mal), tree[lson(k)].val_b + tree[rson(k)].val_f);
    return;//對所有可能的最大連續空房間的長度取max
}
inline void push_down(int k,int l,int r){
    if(tree[k].tag==0)
        return;//若沒有操作,直接返回
    if(tree[k].tag==1){//若將這個區間全部退房,那麼這個區間的所有值都為區間長度
        tree[lson(k)].tag = 1;
        tree[rson(k)].tag = 1;
        tree[lson(k)].val_mal = tree[lson(k)].len;
        tree[rson(k)].val_mal = tree[rson(k)].len;
        tree[lson(k)].val_f = tree[lson(k)].len;
        tree[rson(k)].val_f = tree[rson(k)].len;
        tree[lson(k)].val_b = tree[lson(k)].len;
        tree[rson(k)].val_b = tree[rson(k)].len;
    }
    if(tree[k].tag==2){//同上,若全部住上人,那麼所有值都設為0
        tree[lson(k)].tag = 2;
        tree[rson(k)].tag = 2;
        tree[lson(k)].val_mal = 0;
        tree[rson(k)].val_mal = 0;
        tree[lson(k)].val_f = 0;
        tree[rson(k)].val_f = 0;
        tree[lson(k)].val_b = 0;
        tree[rson(k)].val_b = 0;
    }
    tree[k].tag = 0;//清空標記
    return;
}
void build(int k,int l,int r){
    tree[k].tag = 0;
    tree[k].val_mal = r - l + 1;
    tree[k].val_f = r - l + 1;
    tree[k].val_b = r - l + 1;
    tree[k].len = r - l + 1;//一開始所有的房間均為空
    if(l==r)
        return;
    int mid = (l + r) >> 1;
    build(lson(k), l, mid);
    build(rson(k), mid + 1, r);
    push_up(k);
}
void update(int k,int cl,int cr,int l,int r,int v){
    if(cl<=l&&r<=cr){
        tree[k].tag = v;//若當前區間完全被包含在修改區間中,直接修改,更新lazytag
        if(v==1){
            tree[k].val_f = tree[k].len;
            tree[k].val_b = tree[k].len;
            tree[k].val_mal = tree[k].len;
        }
        if(v==2){
            tree[k].val_f = 0;
            tree[k].val_b = 0;
            tree[k].val_mal = 0;
        }
        return;
    }
    push_down(k, l, r);
    int mid = (l + r) >> 1;
    if(cl<=mid)//基礎線段樹操作
        update(lson(k), cl, cr, l, mid, v);
    if(cr>mid)
        update(rson(k), cl, cr, mid + 1, r, v);
    push_up(k);
}
int query(int k,int qlen,int l,int r){
    push_down(k, l, r);
    if(l==r)
        return l;
    int mid = (l + r) >> 1;
    if(tree[lson(k)].val_mal>=qlen)//由於題目要找的是最左邊的端點,所以儘量往左找
        return query(lson(k), qlen, l, mid);
    else if(tree[lson(k)].val_b+tree[rson(k)].val_f>=qlen)
        return mid - tree[lson(k)].val_b + 1;//返回區間長度
    else//在左邊實在是找不到再往右找
        return query(rson(k), qlen, mid + 1, r);
}
int main(){
    scanf("%d%d", &n, &m);
    build(1, 1, n);//建樹……
    for (int i = 1; i <= m;++i){
        int opt = 0;
        scanf("%d", &opt);
        if(opt==1){
            int x = 0, ls = 0;
            scanf("%d", &x);
            if(tree[1].val_mal>=x){
                ls = query(1, x, 1, n);
                printf("%d\n", ls);
                update(1, ls, ls + x - 1, 1, n, 2);
                //這裡如果找到不要忘記更新!
            }
            else
                puts("0");
        }
        if(opt==2){
            int l = 0, r = 0;
            scanf("%d%d", &l, &r);
            update(1, l, l + r - 1, 1, n, 1);
            //這裡注意操作區間是l~l+r-1,而不是l~r
        }
    }
    return 0;
}