1. 程式人生 > >題解 P1886 【滑動視窗】

題解 P1886 【滑動視窗】

線段樹優化做法

如果仔細讀過題的話,就會發現這是一個靜態的區間查詢最大值與最小值。

很多人(如果你學過線段樹的話)就會想到,我當年學線段樹的例題不就是區間加,然後求區間最大值嗎?何況還沒有區間加這一操作,豈不嗨皮哉???

好的,看看資料範圍。

1*10^6???

線段樹能過的去嗎?還需要維護兩個值,不得兩棵線段樹嗎???

這個問題就不難解決了:

首先是時間的問題。我們的常識告訴我們,O(nlogn)只能走100000,但是如果你的常數不是很大的話,單從線段樹的時間複雜度上說,nlog₂n在n=1000000的話是1000000*20=20000000,加點常數還是可以過的。

接下來解決很多人總要寫兩棵線段樹的問題。由上文知,20000000要求你的常數很小,而你如果寫兩棵線段樹會增大你的常數,而且寫起來費事,所以不如只寫一棵線段樹。

怎麼寫呢???

在此之前先了解一下我的巨集定義QAQ,方便理解以下程式碼

#define root 1,n,1          //根節點的左右邊界與節點編號
#define lson l,m,rt<<1      //左兒子的左右邊界與節點編號
#define rson m+1,r,rt<<1|1  //右兒子的左右邊界與節點編號

我們不妨寫一個結構體

struct node{
    int maxx,minn;//最大值與最小值
}t, z[mn<<2];//t是用來接query函式(一會再講)的返回值的
             //z[]是用來存線段樹主體的。

直接一起存,是不是省了兩棵線段樹的麻煩?

那麼怎麼更新呢?分別更新唄:

inline void update(int rt){//rt表示要更新的線段樹節點
    z[rt].maxx=max(z[rt<<1].maxx,z[rt<<1|1].maxx);
    z[rt].minn=min(z[rt<<1].minn,z[rt<<1|1].minn);
}

這樣就可以更新當前節點啦!

我們的基礎數值可以和建樹操作一起進行。

inline void build(intl,int r,int rt){//建樹
    if(l==r){z[rt].minn=z[rt].maxx=read();}//這裡一起進行(我用了個讀入優化)
    int m=(l+r)>>1;
    build(lson);//左兒子
    build(rson);//右兒子
    update(rt);
}

因為我們沒有區間值的變化,所以就不寫modify這部分了。接下來是一個查詢的過程,這裡有個細節,你要同時返回最小值與最大值,不能把左邊和右邊的一組答案直接作為這一個區間的答案,所以你只能把左右兩邊同時比較最大值最小值,最大值取兩者中最大值最大,最小值取兩者中最小值最小,然後合併成一個node結構體。

所以,為了簡化這一步驟,可以多寫一個函式:

inline node cmp(node a,node b){//函式名字就不要深究了
    return (node){a.maxx > b.maxx ? a.maxx : b.maxx, a.minn < b.minn ? a.minn : b.minn};
}

然後用在查詢(query)函式中:

這裡有個小小的優化:

如果你的判斷是隻有兩個的話,這樣會多次進入迴圈,所以可以直接從判斷上省去一部分時間。

inline node query(int l,int r,int rt,int nowl,int nowr){
    //變數分別是線段樹左右端點,線段樹節點編號,查詢範圍左右端點
    if(nowl<=l && r<=nowr){return z[rt];}
    int m=(l+r)>>1;
    if(nowl<=m){
        if(m<nowr)
            return cmp(query(lson,nowl,nowr),query(rson,nowl,nowr));//直接遞迴
        else
            return query(lson,nowl,nowr);
    }else{
        return query(rson,nowl,nowr);
    }
}

這樣返回的就是區間的最大值和最小值了

主函式的部分直接在完整程式碼中詳細講解了

程式碼:

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#define go(i, j, n, k) for (int i = j; i <= n; i += k)
#define fo(i, j, n, k) for (int i = j; i >= n; i -= k)
#define rep(i, x) for (int i = h[x]; i; i = e[i].nxt)
#define mn 1000100
#define inf 1 << 30
#define ll long long
#define ld long double
#define fi first
#define se second
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define bson l, r, rt
inline int read(){
    int f = 1, x = 0;char ch = getchar();
    while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
    while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
inline void write(int x){
    if (x < 0)putchar('-'),x = -x;
    if (x > 9)write(x / 10);
    putchar(x % 10 + '0');
}
//This is AC head above...
struct node{
    int maxx, minn;
} t,z[mn<<2];
inline node cmp(node a,node b){
    return (node){a.maxx > b.maxx ? a.maxx : b.maxx, a.minn < b.minn ? a.minn : b.minn};
}
inline void update(int rt){
    z[rt].maxx = max(z[rt << 1].maxx, z[rt << 1 | 1].maxx);
    z[rt].minn = min(z[rt << 1].minn, z[rt << 1 | 1].minn);
}
inline void build(int l,int r,int rt){
    if(l==r){
        z[rt].maxx = z[rt].minn = read();
        return;
    }
    int m = (l + r) >> 1;
    build(lson);
    build(rson);
    update(rt);
}
inline node query(int l,int r,int rt,int nowl,int nowr){
    if(nowl<=l && r<=nowr){
        return z[rt];
    }
    int m = (l + r) >> 1;
    if(nowl<=m){
        if(m<nowr)
            return cmp(query(lson, nowl, nowr), query(rson, nowl, nowr));
        else
            return query(lson, nowl, nowr);
    }else{
        return query(rson, nowl, nowr);
    }
}
int n, k;
int a[mn];
int main(){
    n = read(), k = read();//讀入
    build(root);//建樹(並且直接讀入葉節點數值)
    go(i,1,n-k+1,1){//這裡的迴圈用來求區間最大值最小值,順便輸出最小值
        node ooo = query(root, i, i + k - 1);//k是區間的固定長度,所以直接按長度與迴圈到的左端點詢問
        printf("%d ", ooo.minn);//輸出最小值(不敢用cin了QAQ)
        a[i] = ooo.maxx;//儲存最大值
    }
    putchar('\n');
    go(i,1,n-k+1,1){
        printf("%d ", a[i]);//輸出最大值(不敢用cin了)
    }
    return 0;
}

第九次寫題解,希望可以幫到用線段樹卻慘遭TLE的同學