8.16.NOIP模擬賽(線段樹求連續區間長度 | 前綴和單調棧)——2019暑假篇

這次又考栽了

一.題目

1.Hotel

1.1.題目描述

傳送門

1.2.題解

這道題目就是裸的線段樹求最長連續區間長度,修改+查詢的題目。

對於每個區間要定義:最長連續區間,左起最長連續區間,右起最長連續區間,懶標記,左右端點

下面一個一個的來說細節:

1.修改

找到被要修改區間覆蓋的區間,修改它,修改時要注意如何更新區間的狀態:

//修改區間
void Insert (int flag, int Index, int l, int r){
    push_down (Index);//懶標記下方
    if (tre[Index].L >= l && tre[Index].R <= r){
        if (flag == 1)//表示入住酒店
            tre[Index].lazy = 1, tre[Index].Max = tre[Index].Maxr = tre[Index].Maxl = 0;
        if (flag == -1)//表示退房
            tre[Index].lazy = -1, tre[Index].Max = tre[Index].Maxr = tre[Index].Maxl = tre[Index].R - tre[Index].L + 1;
        return ;
    }
    if (tre[Index].L > r || tre[Index].R < l)
        return ;
    Insert (flag, Index * 2, l, r);
    Insert (flag, Index * 2 + 1, l, r);
    renew (Index);//更新區間狀態
}
void renew (int Index){
    if (tre[Index * 2].Max == tre[Index * 2].R - tre[Index * 2].L + 1)//最長左區間
        tre[Index].Maxl = tre[Index * 2].Max + tre[Index * 2 + 1].Maxl;
//如果左區間全爲空,最長左區間就爲左區間長度+右區間的最長左區間
    else
        tre[Index].Maxl = tre[Index * 2].Maxl;
    if (tre[Index * 2 + 1].Max == tre[Index * 2 + 1].R - tre[Index * 2 + 1].L + 1)//最長右區間同理
        tre[Index].Maxr = tre[Index * 2 + 1].Max + tre[Index * 2].Maxr;
    else
        tre[Index].Maxr = tre[Index * 2 + 1].Maxr;
    tre[Index].Max = max (tre[Index * 2].Max, tre[Index * 2 + 1].Max);//最長區間
    tre[Index].Max = max (tre[Index].Max, tre[Index * 2].Maxr + tre[Index * 2 + 1].Maxl);
}

2.查詢

很簡單,從左區間開始,看它是否有這麼多的空房,再看中間的交界,再看右區間

int Query (int Index, int d){
    push_down (Index);
    if (tre[Index].L == tre[Index].R)//說明找到了
        return tre[Index].L;
    if (tre[Index * 2].Max >= d)//看左區間
        return Query (Index * 2, d);
    if (tre[Index * 2].Maxr + tre[Index * 2 + 1].Maxl >= d)//看中間
        return tre[Index * 2].R - tre[Index * 2].Maxr + 1;
    if (tre[Index * 2 + 1].Max >= d)//看右區間
        return Query (Index * 2 + 1, d);
    return -1;
}

1.3.Code

//hotel
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

#define M 50005

int n, m, ans;
struct node {
    int L, R, lazy, Max, Maxl, Maxr;
}tre[M * 40];

void build (int Index, int l, int r){
    tre[Index].L = l;
    tre[Index].R = r;
    tre[Index].Max = tre[Index].Maxl = tre[Index].Maxr = r - l + 1;
    if (l != r){
        int mid = (l + r) / 2;
        build (Index * 2, l, mid);
        build (Index * 2 + 1, mid + 1, r);
    }
}
void push_down (int Index){
    if (tre[Index].lazy == 1){
        tre[Index * 2].Max = tre[Index * 2].Maxl = tre[Index * 2].Maxr = 0;
        tre[Index * 2 + 1].Max = tre[Index * 2 + 1].Maxl = tre[Index * 2 + 1].Maxr = 0;
        tre[Index * 2].lazy = tre[Index * 2 + 1].lazy = 1;
    }
    if (tre[Index].lazy == -1){
        tre[Index * 2].Max = tre[Index * 2].Maxl = tre[Index * 2].Maxr = tre[Index * 2].R - tre[Index * 2].L + 1;
        tre[Index * 2 + 1].Max = tre[Index * 2 + 1].Maxl = tre[Index * 2 + 1].Maxr = tre[Index * 2 + 1].R - tre[Index * 2 + 1].L + 1;
        tre[Index * 2].lazy = tre[Index * 2 + 1].lazy = -1;
    }
    tre[Index].lazy = 0;
}
int Query (int Index, int d){
    push_down (Index);
    if (tre[Index].L == tre[Index].R)
        return tre[Index].L;
    if (tre[Index * 2].Max >= d)
        return Query (Index * 2, d);
    if (tre[Index * 2].Maxr + tre[Index * 2 + 1].Maxl >= d)
        return tre[Index * 2].R - tre[Index * 2].Maxr + 1;
    if (tre[Index * 2 + 1].Max >= d)
        return Query (Index * 2 + 1, d);
    return -1;
}
void renew (int Index){
    if (tre[Index * 2].Max == tre[Index * 2].R - tre[Index * 2].L + 1)
        tre[Index].Maxl = tre[Index * 2].Max + tre[Index * 2 + 1].Maxl;
    else
        tre[Index].Maxl = tre[Index * 2].Maxl;
    if (tre[Index * 2 + 1].Max == tre[Index * 2 + 1].R - tre[Index * 2 + 1].L + 1)
        tre[Index].Maxr = tre[Index * 2 + 1].Max + tre[Index * 2].Maxr;
    else
        tre[Index].Maxr = tre[Index * 2 + 1].Maxr;
    tre[Index].Max = max (tre[Index * 2].Max, tre[Index * 2 + 1].Max);
    tre[Index].Max = max (tre[Index].Max, tre[Index * 2].Maxr + tre[Index * 2 + 1].Maxl);
}
void Insert (int flag, int Index, int l, int r){
    push_down (Index);
    if (tre[Index].L >= l && tre[Index].R <= r){
        if (flag == 1)
            tre[Index].lazy = 1, tre[Index].Max = tre[Index].Maxr = tre[Index].Maxl = 0;
        if (flag == -1)
            tre[Index].lazy = -1, tre[Index].Max = tre[Index].Maxr = tre[Index].Maxl = tre[Index].R - tre[Index].L + 1;
        return ;
    }
    if (tre[Index].L > r || tre[Index].R < l)
        return ;
    Insert (flag, Index * 2, l, r);
    Insert (flag, Index * 2 + 1, l, r);
    renew (Index);
}
int main (){
    freopen ("hotel.in", "r", stdin);
	freopen ("hotel.out", "w", stdout);
    int f, x, d;
    scanf ("%d %d", &n, &m);
    build (1, 1, n);
    while (m --){
        scanf ("%d", &f);
        if (f == 1){
            scanf ("%d", &d);
            ans = Query (1, d);
            if (ans == -1){
                printf ("0\n");
                continue;
            }
            printf ("%d\n", ans);
            Insert (1, 1, ans, ans + d - 1);
        }
        if (f == 2){
            scanf ("%d %d", &x, &d);
            Insert (-1, 1, x, x + d - 1);
        }
    }
    return 0;
}

2.sequence

2.1.題目

題目描述

給定n個正整數的序列a1,a2,a3…an,對該序列可執行下面的操作: 選擇一個大於k的正整數ai,將ai的值減去1;選擇ai-1或ai+1中的一個值加上1。 共給出m個正整數k,對於每次給定的正整數k,經過以上操作一定次數後,求出最長的一個連續子序列,使得這個子序列的每個數都不小於給定的k

輸入格式

第一行兩個正整數n和m。 第二行n個正整數,第i個正整數表示ai ; 第三行m個正整數,第i個正整數表示第i次給定的k。

輸出格式

共一行,輸出m個正整數,第i個數表示對於第i次給定的k,經過一定次數操作後,最長連續子序列的長度。

樣例輸入

5 6
1 2 1 1 5
1 2 3 4 5 6

輸出樣例

5 5 2 1 1 0

數據範圍與提示

對於 30%的數據,n<30 對於 50%的數據,n<=2000 對於 100%的數據,n<=1,000,000 ,m<= 50, k <= 10^9, ai <= 10^9

2.2.題解

這道題目要注意關鍵詞語:最長連續區間。

所以樸素的O\left ( n^{2} \right )算法就來了:求前綴和,前後枚舉前綴和相減求最長區間


優化一點的,枚舉後面的前綴和,發現前面的前綴和具有單調性

對於前面的前綴和:越前面的前綴和必須必後面一點的前綴和要大,那麼後面一點的前綴和纔有意義。

所以維護一個單調遞減的單調棧:每遇到比棧頂小的就加入

然後每枚舉到一個後面的前綴和,就二分查找單調棧裏第一個比它小的前綴和,在統計答案即可。

但這樣會被卡


再優一點,先處理出單調棧,如果我們從最後開始枚舉後面的那一個前綴和,找到單調棧裏第一個比它小的前綴和,把單調棧裏後面的前綴和去掉,每次就這樣操作就行了。

爲什麼呢?你想對於後面的那一個前綴和:越前面一點的前綴和在單調棧裏對應的第一個比它小的前綴和的位置要比後面一點的前綴和對應的位置靠前才行,要不然要要虧!

好,挺簡單,是吧。

2.3.Code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

#define M 1000005
#define LL long long

int n, m, a[M], Stack[M], top;
bool flag;
LL k, b[M], sum[M];

void print (int ans){
    if (! flag){printf ("%d", ans); flag = 1;}
    else printf (" %d", ans);
}
int main (){
    //freopen ("sequence.in", "r", stdin);
	//freopen ("sequence.out", "w", stdout);
    scanf ("%d %d", &n, &m);
    for (int i = 1; i <= n; i ++){
        scanf ("%d", &a[i]);
        sum[i] = sum[i - 1] + a[i];
    }
    while (m --){
        int ans = 0;
        top = 0;
        scanf ("%lld", &k);
        Stack[++ top] = 0;
        for (int i = 1; i <= n; i ++){
            b[i] = sum[i] - i * k;
            if (b[i] < b[Stack[top]])
                Stack[++ top] = i;
        }
        for (int i = n; i >= 1; i --){
            int wei = i;
            if (b[Stack[1]] <= b[i]){
                ans = max (ans, i - Stack[1]);
                wei = Stack[1];
                top = 1;
            }
            else if (b[Stack[top]] <= b[i]){
                while (b[Stack[top]] <= b[i] && top)
                    top --;
                top ++;
                ans = max (ans, i - Stack[top]);
            }
        }
        print (ans);
    }
    return 0;
}

3.run

盡情期待。

二.總結

盡情期待.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章