--- tags: 109_FDCS --- # segment tree part 2 ## 懶標記 如果題目有需要用到區間查詢和區間修改 最直覺的解法是把修改的區間每個點都分別做修改 ```cpp= int x; //x為欲修改成的值 int ql, qr; //[ql, qr)為欲修改的區間 for(int i = ql; i < qr; i++) update(i, x); ``` 不過會發現一個問題,複雜度好大 $O((qr - ql)logn)$ 其中 $n$ 為線段樹的大小 --- 這時候可以用一個方法解決 每次在更新時不直接更新到根節點 而是用一個標記紀錄還有多少值沒有更新 代碼如下(例題: [DJ a408: 區間更新](http://203.64.191.163/ShowProblem?problemid=a408)) ```cpp= constexpr int maxn = 1e6 + 5; int n; ll seg[maxn << 2], tag[maxn << 2], arr[maxn]; #define m ((l + r) >> 1) inline void push(int i, int l, int r) { if (!tag[i]) return; if (r - l == 1) {seg[i] += tag[i], tag[i] = 0; return;} seg[i] += (r - l) * tag[i]; tag[i << 1] += tag[i], tag[i << 1 | 1] += tag[i], tag[i] = 0; } void build(int i = 1, int l = 0, int r = n) { if (r - l == 1) {seg[i] = arr[l]; return;} build(i << 1, l, m), build(i << 1 | 1, m, r); seg[i] = seg[i << 1] + seg[i << 1 | 1]; } void rmodify(int ql, int qr, int x, int i = 1, int l = 0, int r = n) { push(i, l, r); if (qr <= l || r <= ql) return; if (ql <= l && r <= qr) {tag[i] += x, push(i, l, r); return;} rmodify(ql, qr, x, i << 1, l, m), rmodify(ql, qr, x, i << 1 | 1, m, r); seg[i] = seg[i << 1] + seg[i << 1 | 1]; } ll query(int ql, int qr, int i = 1, int l = 0, int r = n) { push(i, l, r); if (qr <= l || r <= ql) return 0LL; if (ql <= l && r <= qr) return seg[i]; return query(ql, qr, i << 1, l, m) + query(ql, qr, i << 1 | 1, m, r); } #undef m inline void solve() { int q, op, l, r, k; cin >> n >> q; for (int i = 0; i < n; i++) cin >> arr[i]; build(); while (q--) { cin >> op; if (op) cin >> l >> r, cout << query(l - 1, r) << endl; else cin >> l >> r >> k, rmodify(l - 1, r, k); } } ``` --- ## 動態開點 當你的 $n$ 很大,操作比較少 i.e. $n=10^9,op=10^5$, $op$ 為修改次數 但是你無法壓縮 $n$ 的時候就可以用動態開點 (如果有辦法壓縮,用離散化會更好) 核心概念是有更新到才分配記憶體 因為陣列版在一開始就分配了所有記憶體 所以動態開點會用指標實作 代碼如下(例題: [DJ a491: schedule (hard version)](http://203.64.191.163/ShowProblem?problemid=a491)) ```cpp= constexpr int n = 1e9 + 5; struct node { int v; node *l, *r; }*root = nullptr; #define m ((l + r) >> 1) void update(int p, int x, node*& cur = root, int l = 0, int r = n) { if (p < l || r <= p) return; if (!cur) cur = new node{0, nullptr, nullptr}; cur->v += x; if (r - l == 1) return; update(p, x, cur->l, l, m), update(p, x, cur->r, m, r); } int query(int ql, int qr, node* cur = root, int l = 0, int r = n) { if (!cur || qr <= l || r <= ql) return 0; if (ql <= l && r <= qr) return cur->v; return query(ql, qr, cur->l, l, m) + query(ql, qr, cur->r, m, r); } #undef m inline void solve() { int m, a, b, k; cin >> m; while (m--) { string str; cin >> str; if (str == "live") cin >> a >> b, update(a, 1), update(b, -1); if (str == "tab") cin >> k, cout << query(0, k + 1) << endl; } } ``` --- ## 補充: 離散化 另一種降低$n$大小的方法 當一個陣列裡面元素的準確值不重要,只需要比較大小的時候 就可以用離散化來降低最大值的大小 i.e. $n=10^5,max(a_i)=10^9$, $n$ 為陣列 $a$ 的大小 如果離散化之後 $max(a_i)$ 就會降成 $\leq 10^5$ 小於 $n$ 的原因是 $a$ 裡面有重複元素 代碼(模板)如下 ```cpp= int n; cin >> n; vector<int> v(n); for (auto& i : v) cin >> i; //離散化 auto tmp = v; sort(tmp.begin(), tmp.end()); tmp.resize(unique(tmp.begin(), tmp.end()) - tmp.begin()); for (auto& i : v) i = lower_bound(tmp.begin(), tmp.end(), i) - tmp.begin(); ``` 範例(例題: [DJ a095: pE 好動的小朋友有糖吃](http://203.64.191.163/ShowProblem?problemid=a095)) ```cpp= inline void solve() { int n; cin >> n; vector<pair<int, long long>> v(n); for (auto& i : v) cin >> i.first; for (auto& i : v) cin >> i.second; int height; /*離散化*/{ auto tmp = v; sort(tmp.begin(), tmp.end()); tmp.resize(unique(tmp.begin(), tmp.end(), [](const auto & a, const auto & b) { return a.first == b.first; }) - tmp.begin()); for (auto& i : v) i.first = lower_bound(tmp.begin(), tmp.end(), i, [](const auto & a, const auto & b) { return a.first < b.first; }) - tmp.begin(); height = tmp.size(); } segment_tree tree(height); for (auto i : v) if ((i.second += tree(0, i.first)) > tree.data[i.first + height]) tree.modify(i.first, i.second); cout << tree(0, height) << endl; } ``` 底下線段樹<ruby>實作方法<rt>z k w</rt></ruby>比較毒,可以參考上面離散化的部分就好