HDU-1540 Tunnel Warfare 題解 線段樹特殊儲存/set 集合容器紅黑樹的應用

題目鏈接:Tunnel Warfare

題目

在這裏插入圖片描述

題意

地~道戰 嘿!地~道戰

現在有一條地道,告訴你有 n 個出口,沿着地道是 1,2,3,,n 依次排列的

然後有 m 個操作:

  • D x 表示摧毀一個出口 x,摧毀之後 x 這個出口兩邊的地道不再相連
  • Q x 是訪問於 x 這個出口相連的能用的出口總共有幾個,包括 x
  • R 將上一個被摧毀的出口重建

解題思路

法一:從題意來看,就是一個單點更新和區間求和的題,但問題是如何來求 與這個點相連的能用的點 百思不得其解之後看博客去搞懂了

這個巧解的做法是,儲存這個節點所管理的區間裏

  • 從最左端往右有幾個能用的點
  • 從最右端往左有幾個能用的點
  • 整個區間裏的幾段能用的點,最長的一段有多長

法二:儲存被摧毀的點,被摧毀的點中間的長度就是未摧毀的區間,直接使用 set 集合容器自動排序upper_bound() 方法 進行計算

代碼註釋詳解

代碼

//
// _   _     ___           ____ ___  ____  _____
//| | | |   / _ \ _ __    / ___/ _ \|  _ \| ____|
//| |_| |  | | | | '_ \  | |  | | | | | | |  _|
//|  _  |  | |_| | | | | | |__| |_| | |_| | |___
//|_| |_|___\___/|_| |_|  \____\___/|____/|_____|
//     |_____|
//

#include <iostream>
using namespace std;
typedef int hip;
#include <stack>
const hip maxn = int(5e4 + 9);
#define L t << 1
#define R t << 1 | 1

//定義節點結構體
//l - 左邊界 r - 右邊界
//m - 中間值 w - 區間長度
//a - 從左邊開始的最大長度
//b - 整個區間中的最大長度
//c - 從右邊開始的最大長度
struct P {hip l, r, m, w, a, b, c;} tt[maxn << 2];

//建樹
void build(hip l, hip r, hip t) {
        hip w = r - l + 1;
        tt[t] = {l, r, (l + r) >> 1, w, w, w, w};
        if (l == r) return ;
        build(l, tt[t].m, L);
        build(tt[t].m + 1, r, R);
}

//更新,只能到葉子節點才更新,但是
//更新完子節點以後對於父節點的更新有點講究
void update(hip p, hip n, hip t) {
        //超界直接返回
        if (p < tt[t].l || p > tt[t].r) return ;
        //葉子節點直接更新
        if (tt[t].l == tt[t].r) {tt[t].a = tt[t].b = tt[t].c = n; return ;}
        update(p, n, L); update(p, n, R);
        //更新區間內最長區間的長度 max(左子區間裏的最長,右子區間裏的最長,中間連接的長度)
        tt[t].b = max(max(tt[L].b, tt[R].b), tt[L].c + tt[R].a);
        //左最長就等於左子區間的左最長,如果連起來就加上右子區間的左最長
        tt[t].a = tt[L].a; if (tt[t].a == tt[L].w) tt[t].a += tt[R].a;
        //右最長就等於右子區間的右最長,如果連起來就加上左子區間的右最長
        tt[t].c = tt[R].c; if (tt[t].c == tt[R].w) tt[t].c += tt[L].c;
}

//更新
hip query(hip p, hip t) {
        //如果已經訪問到葉子節點直接返回
        if (tt[t].l == tt[t].r) return tt[t].b;
        //如果請求點在中間連續區間的左邊,往左邊找
        if (p <= tt[t].m - tt[L].c) return query(p, L);
        //如果請求點在中間連續區間的右邊,往右邊找
        if (p > tt[t].m + tt[R].a) return query(p, R);
        //如果就在中間連續的區間中,就返回這個長度
        return tt[L].c + tt[R].a;
}

int main() {
        ios::sync_with_stdio(0); cin.tie(0);
        //記得多實例
        hip n, m; while (cin >> n >> m) {
                //建樹
                build(1, n, 1);
                //用棧容器來儲存摧毀/修復記錄
                stack<hip> s;
                while (m--) {
                        char o; cin >> o;
                        if (o == 'D') {hip p; cin >> p; update(p, 0, 1); s.push(p);}
                        if (o == 'Q') {hip p; cin >> p; cout << query(p, 1) << endl;}
                        if (o == 'R') update(s.top(), 1, 1), s.pop();
                }
        }
        return 0;
}
//
// _   _     ___           ____ ___  ____  _____
//| | | |   / _ \ _ __    / ___/ _ \|  _ \| ____|
//| |_| |  | | | | '_ \  | |  | | | | | | |  _|
//|  _  |  | |_| | | | | | |__| |_| | |_| | |___
//|_| |_|___\___/|_| |_|  \____\___/|____/|_____|
//     |_____|
//

#include <iostream>
using namespace std;
typedef int hip;
#include <set>
#include <stack>

int main() {
        ios::sync_with_stdio(0); cin.tie(0);
        //記得多實例
        hip n, m; while (cin >> n >> m) {
                //用棧容器來儲存摧毀/修復記錄
                stack<hip> s;
                //用 set 集合容器來儲存被摧毀的點
                set<hip> e;
                //爲了防止請求到兩端的區間只有一個邊界
                //加兩個邊界數作爲邊界,方便計算操作
                e.insert(0); e.insert(n + 1);
                while (m--) {
                        char o; cin >> o;
                        if (o == 'D') {hip p; cin >> p; e.insert(p); s.push(p);}
                        if (o == 'Q') {
                                hip p; cin >> p;
                                //如果請求的點在容器內,即已經被摧毀
                                //直接輸出 0
                                if (e.find(p) != e.end()) {cout << 0 << endl; continue;}
                                //從集合中找到一個大於請求的數的數
                                //減去上一個數即爲請求點所在區間
                                auto a = e.upper_bound(p), b = a;
                                //兩個被摧毀的點的數值相減-1就是包含請求點的沒被摧毀的連續點數
                                cout << *a - *--b - 1 << endl;
                        }
                        if (o == 'R') e.erase(s.top()), s.pop();
                }
        }
        return 0;
}

參考文章

HDU-1540 Tunnel Warfare 線段樹最大連續區間 或 STL巧解

請多多支持猹的個人博客,這裏的文章那裏也都有 H_On 個人小站
因爲猹的小站真的還挺可的,所以那邊更新的也比較勤奮,感謝關注~我會努力的(ง •_•)ง

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