資料結構們

小複習

區間最大連續和

spoj GSS3

序列操作題

codeforces ???

  1. \(l, r, x, d\) : 把區間 \([l, r]\) 加上首項為 \(x\) 公差為 \(d\) 的序列
  2. \(l, r\) : 詢問區間和

矩形覆蓋面積計算

tioj 1224

二元搜尋樹 

 

Naïve

binary search tree

  1. \(\mathcal{O}(n)\) insert \(x\) :
    順著找到可以放的位置
  2. \(\mathcal{O}(n)\) erase \(x\) :
    找到它後拔掉 找到子樹中的 lower_bound( \(x\) ) ,然後把它換上來
  3. \(\mathcal{O}(n)\) query \(x\):
    在樹上(搞不好是鍊)二分(所以可能不算二分搜)搜

Tree + Heap = Treap

Treap 

  1. \(\mathcal{O}(log_2 n)\) insert \(x\) :
    ????
  2. \(\mathcal{O}(log_2 n)\) erase \(x\) :
    ????
  3. \(\mathcal{O}(log_2 n)\) query \(x\):
    二分搜

Merge Split Treap

  1. Merge :
    把一個 key 全部都小於等於 \(k\) 的 Treap
    和一個 key 全部都大於等於 \(k\) 的 Treap
    合併起來
  2. Split :
    把一棵 Treap 分割成
    一個 key 全部都小於         \(k\) 的 Treap 和
    一個 key 全部都大於等於 \(k\) 的 Treap
  1. \(\mathcal{O}(log_2 n)\) insert \(x\) :
    把自己 split 成 key < \(x\) 和 key \(\geq x\) 的兩塊 \(a, b\)
    然後合併 \(a, x, b\)
  2. \(\mathcal{O}(log_2 n)\) erase \(x\) :
    把自己 split 成 key < \(x\) 和 key \(\geq x\) 的兩塊 \(a, b\)
    再把 \(b\) split 成 key \(\leq x\) 和 key > \(x\) 的兩塊 \(c, d\)
    然後合併 \(a, c, d\)
  3. \(\mathcal{O}(log_2 n)\) query \(x\):
    二分搜

Merge Split Treap

Operation 1 : Merge

key \(\leq 3\)

key \(\geq 3\)

比較 pri

?

比較 pri

比較 pri

比較 pri

比較 pri

比較 pri

比較 pri

Operation 2 : Split

\(k = 4\)

\(k = 4\)

\(k = 4\)

\(k = 4\)

\(k = 4\)

實做

 

unsigned ran() {
    static unsigned x = 19;
    return ++(x *= 0xdefaced);
}
struct node {
    node * left = NULL, * right = NULL;
    int key;
    unsigned pri;
    node(int key):key(key), pri(ran()){};
};

實做

 

node * merge (node* a, node* b) {
    if (!a)return b;
    if (!b)return a;
    if (a->pri < b->pri) {
        a->right = merge(a->right, b);
        return a;
    }
    else {
        b->left = merge(a, b->left);
        return b;
    }
}

實做

 

void split(node* now, int key, node* &a, node* &b) {
    if (!now) {
        a = b = NULL;
        return;
    }
    if (now->key < key) {
        a = now;
        split(now->right, key, a->right, b);
    }
    else {
        b = now;
        split(now->left, key, a, b->left);
    }
}

關於 treap 小技巧

  1. 其實 pri 值只有用在 merge 決定上下的時候
    所以其實可以在那個時候 再決定就好
    可以發現若兩棵樹的大小分別是 \(S_a, S_b\),那 麼 \(a\) 應該要在上層的機率約是 \(\frac{S_a}{S_a + S_b}\)
  2. 如果順便維護子樹的大小,那就可以在樹上二分搜求出第 \(k\) 大的元素
  3. 在序列上,把 index 視為 key,則可以建出一個完整序列的 Treap,查詢區間時,只要把整格區間切下來並且維護子樹的資訊就好。可以做到線段樹支援的事情(只是比較慢....

TNFSHOJ 31
大榕樹的祈禱

維護一個序列,操作有以下

  1. \(\text{insert}  p  k  v_1, v_2, v_3... v_k\):
    把 \(k\) 個數字\(v_1, v_2, v_3...v_k\)插入在第 \(p\) 個數字的後面
  2. \(\text{delete}  p  k \):
    從第 \(p\) 個數字開始連續刪除 \(k\) 個數字
  3. \(\text{make-same}  p  k  v\) : 把 \(p\) 開始的連續 \(k\) 數字都改成 \(v\)
  4. \(\text{reverse}  p  k\) : 把 \(p\) 開始的連續 \(k\) 個數字倒轉
  5. \(\text{get-sum}  p  k\) : 計算從 \(p\) 開始的 \(k\) 個數字的和
  6. \(\text{max-sum}\) : 計算目前整個數列中最大的連續和

小例題

請維護一個集合,使得他支援兩種操作 :

  1. 加入一個數字 \(x\)
  2. 詢問目前第 \(k\) 大的數字

\(x \leq 10^5\)

操作數量 \(\leq 10^5\)

先假設線段樹不是代表一個序列,而是值域?

小例題

請維護一個集合,使得他支援兩種操作 :

ojdl 7129

  1. 加入一個數字 \(x\)
  2. 詢問目前第 \(k\) 大的數字

# : \(x \leq 10^9\)

操作數量 \(\leq 3 \cdot10^5\)

需要再開!

持久化

隕石

有一些城市 編號由 \(1 ~ n\)

有一些科學家想採集隕石樣本,而第 \(i\) 個城市需要採集 \(p_i\) 個

有 \(k\) 筆事件兩兩事件時間差為 1 且按照時間順序 \(l_i, r_i, a_i\) 表達在從 \([l_i, r_i]\) 都將會有 \(a_i\) 顆隕石墜落

請輸出 \(n\) 行代表在第 \(i\) 座城市至少要多久才能完成樣本採集 

 

\(1 \leq n \leq 300000, 1 \leq k \leq 300000\)

 

 

如果我有 \(n\) 顆線段樹

第 \(i\) 顆線段樹紀錄了時間從 \([1, i]\) 的所有事件,

那就對於每個 \(i\) 就可以二分搜答案了!

持久化線段樹

 

 

實作

 

struct node {
    node *left = NULL, *right = NULL;
    int val;
};

實做

 

node* add(node* old, int pos, int val) {
    node* res = new node(old), * now = res;
    int l = 1, r = n, m;
    res->val += val;
    while (l < r) {
        m = l + r >> 1;
        if (pos > m) {
            now->right = new node(old->right);
            now->right->val += val;
            now = now->right;
            old = old->right;
            r = m;
        }
        else {
            now->left = new node(old->left);
            now->left->val += val;
            now = now->left;
            old = old->left;
            l = m+1;
        }
    }
    return res;
}

實作

 

node* add(node* old, int pos, int val) {
    node* res = new node(old), * now = res;
    int l = 1, r = n, m;
    res->val += val;
    while (l < r) {
        m = l + r >> 1;
        if (pos > m) {
            now->right = new node(old->right);
            now->right->val += val;
            now = now->right;
            old = old->right;
            r = m;
        }
        else {
            now->left = new node(old->left);
            now->left->val += val;
            now = now->left;
            old = old->left;
            l = m+1;
        }
    }
    return res;
}

例題

POJ2104

靜態區間第 \( K \) 大

TIOJ 1975

有兩種工作,總共有 n 個

第一種會佔用一個處理器 使用從 \(l_i\) 到 \(r_i\) 的所有時間 並且從頭到尾只能使用同一個處理器

第二種有完成期限,可以分配給不同的處理器,必須在 \(d_i\)之前完成,需要花 \(w_i\) 的時間

請問至少需要幾個處理器 才能完成所有工作

NOTE : 一個處理器在任何時刻都只能處理一個工作
第二種工作在任何時刻也最多只能被一個處理器處理

\(l_i \leq\ r_i \leq 10^6\)

\(n \leq 10^5\)

 

TIOJ 1840

帶修改區間 \(K\) 大

BIT +

動態開點值域線段樹

struct bit {
    node* root[maxn];
    void add(int x, int y, int val) {
        for (;x <= maxn;x += x & -x)
            modify(root[x], y, val);
    }
    
};

struct bit{    
    void shift(vector<node*> &nodes, int dir) {
        static const l_type = 0;
        for (auto &i : nodes) i = (dir == l_type ? i->left : i->right);
    }
    int left_sum(vector<node*> &nodes) {
        int res = 0;
        for (auto i : nodes) res += i->left->cnt;
        return res;
    }
    int query(int l, int r, int k) {
        vector<node*> lnode, rnode;
        for (int i = l;i;i ^= i & -i) lnode.pb(root[i]);
        for (int i = r;i;i ^= i & -i) rnode.pb(root[i]);
        int L = 1, R = n, M;
        while (L < r) {
            M = L + R >> 1;
            if (int lcnt = left_sum(rnode) - left_sum(lnode) ; lcnt >= k) {
                shift(rnode, 0), shift(lnode, 0);
                R = M;
            }
            else {
                k -= lcnt;
                shift(rnode, 1), shift(lnode, 1);
                L = M+1;
            }
        }
        return L;
    }
};

最長遞增子序列 LIS

map 維護單調隊列

\(dp_i := \) 以數值 \(i\) 結尾最長的答案

用 map<int,int> dp;

有 \(q, (q \leq 10^5)\) 筆操作

  1. 加進一筆資料 \(x, y, w, (x \leq 10^5, y \leq 10^5, w \leq 10^9)\)
  2. 詢問 \(a, b\) :
    輸出在所有資料中 \(x \leq a, y \leq b\) 中最大的 \(w\)

最後一題

在碼頭上,你擁有一個碼頭,根據政府規定,一個碼頭一天最多只能有兩艘船停靠卸貨,你手上有一份名單上面有

\( n \leq 10^5 \) 個資訊,告訴你今天所有船的

停靠時間,離開時間,以及獲利

\(l_i, r_i, c_i, (l_i \leq r_i \leq 10^5, c_i \leq 10^9)\) 

在任意時刻,一個碼頭最多都只能停靠一艘船,你可以選擇要不要讓他停靠,請問你今天最多能夠獲利多少呢?

 

 

Made with Slides.com