slides : mouyilai
edit : lemon
高三最近比較忙QAQ
所以跟學姊借了一份簡報。
大部分我想講的應該會直接畫在白板上(?
然後就是
線段樹是一個很重要的技術
一定要學好ㄛ!
給你一個長度為 \(N\) 的序列和 \(Q\) 次詢問,有兩種詢問
1. 把 index 為\(x\) 的值改成 \(k\)
2. 查詢 \([a, b]\) 區間的最小值
(\(N, Q \leq 2\times 10^5\))
最直接的想法: 全部掃過一次
複雜度 \(O(NQ)\) 成功 TLE
殼以用線段樹 讚
第二直接的想法: 前綴min
然後改值就爛ㄌ
然後我們就可以開始蓋線段樹ㄌ
\(a= \{3,\ 2,\ 4,\ 5,\ 1,\ 1,\ 5,\ 3\}\)
vector <ll> segtree, a;
void pull(int x) { //用兩個子節點來更新現在的節點的答案
segtree[x] = min(segtree[2 * x], segtree[2 * x + 1]);
}
利用兩個子節點更新答案!
void build(int l, int r, int x) { //用index是x的節點來存[l, r]的答案
if(l == r) { //葉節點
segtree[x] = a[l];
return;
}
int mid = (l + r) / 2;
build(l, mid, 2 * x), build(mid + 1, r, 2 * x + 1);
pull(x); //要記得更新!
}
void modify(int p, int v, int l, int r, int x) {
//把index為p的節點的值改為v 現在在的節點是index為x,存的是[l, r]的答案
if(l == r) { //找到ㄌ
segtree[x] = v;
return;
}
int mid = (l + r) / 2;
if(p <= mid) modify(p, v, l, mid, 2 * x); //往左子節點遞迴
else modify(p, v, mid + 1, r, 2 * x + 1); //往右子節點遞迴
pull(x); //用左右子節點的答案更新x的答案
}
每次從中間切,如果要修改的點在左子樹就往左邊遞迴,在右邊就往右邊遞迴
假設我現在在的節點存的是\([l, r]\)的答案,\(mid= \lfloor \frac{l+r}{2}\rfloor\)
我想查詢\([ql, qr]\)的答案
有四種情況
ll query(int ql, int qr, int l, int r, int x) {
if(ql == l && qr == r) return segtree[x];
int mid = (l + r) / 2;
if(qr <= mid) return query(ql, qr, l, mid, 2 * x);
else if(mid < ql) return query(ql, qr, mid + 1, r, 2 * x + 1);
return min(query(ql, mid, l, mid, 2 * x), query(mid + 1, qr, mid + 1, r, 2 * x + 1));
}
好耶做完ㄌ
using namespace std;
#include <bits/stdc++.h>
typedef long long ll;
vector <ll> segtree, a;
void pull(int x) {
segtree[x] = min(segtree[2 * x], segtree[2 * x + 1]);
}
void build(int l, int r, int x) {
if(l == r) {
segtree[x] = a[l];
return;
}
int mid = (l + r) / 2;
build(l, mid, 2 * x), build(mid + 1, r, 2 * x + 1);
pull(x);
}
void modify(int p, int v, int l, int r, int x) {
if(l == r) {
segtree[x] = v;
return;
}
int mid = (l + r) / 2;
if(p <= mid) modify(p, v, l, mid, 2 * x);
else modify(p, v, mid + 1, r, 2 * x + 1);
pull(x);
}
ll query(int ql, int qr, int l, int r, int x) {
if(ql == l && qr == r) return segtree[x];
int mid = (l + r) / 2;
if(qr <= mid) return query(ql, qr, l, mid, 2 * x);
else if(mid < ql) return query(ql, qr, mid + 1, r, 2 * x + 1);
return min(query(ql, mid, l, mid, 2 * x), query(mid + 1, qr, mid + 1, r, 2 * x + 1));
}
int main() {
int n, q;
cin >> n >> q;
segtree.resize(4 * n);
a.resize(n + 1);
for(int i = 1; i <= n; ++i) cin >> a[i];
build(1, n, 1);
while(q--) {
int op, a, b;
cin >> op >> a >> b;
if(op == 1) modify(a, b, 1, n, 1);
else cout << query(a, b, 1, n, 1) << '\n';
}
return 0;
}
using namespace std;
#include <bits/stdc++.h>
typedef long long ll;
const ll INF = 1e18;
struct Node{
Node *lc, *rc;
ll val;
void pull() {
val = min(lc->val, rc->val);
}
};
Node *root = nullptr;
Node *build(int l, int r, vector <int> &a) {
Node *nd = new Node();
if(l == r) {
nd->lc = nd->rc = nullptr;
nd->val = a[l];
}
else {
int mid = (l + r) / 2;
nd->lc = build(l, mid, a), nd->rc = build(mid + 1, r, a);
nd->pull();
}
return nd;
}
void modify(Node *nd, int p, int v, int l, int r) {
if(l == r) {
nd->val = v;
return;
}
int mid = (l + r) / 2;
if(p <= mid) modify(nd->lc, p, v, l, mid);
else modify(nd->rc, p, v, mid + 1, r);
nd->pull();
}
ll query(Node *nd, int ql, int qr, int l, int r) {
if(r < ql || qr < l) return INF;
if(ql <= l && r <= qr) return nd->val;
int mid = (l + r) / 2;
return min(query(nd->lc, ql, qr, l, mid), query(nd->rc, ql, qr, mid + 1, r));
}
int main() {
int n, q;
cin >> n >> q;
vector <int> a(n + 1);
for(int i = 1; i <= n; ++i) cin >> a[i];
root = build(1, n, a);
while(q--) {
int op, a, b;
cin >> op >> a >> b;
if(op == 1) modify(root, a, b, 1, n);
else cout << query(root, a, b, 1, n) << '\n';
}
return 0;
}
例子
把index為3的值增加1
\(4=3 + lowbit(3)\)
=> ++BIT[3], ++BIT[4], ++BIT[8]
\(8=4 + lowbit(4)\)
每次往右走\(lowbit(x)\)
查詢index14的前綴和
\(8=12 + lowbit(12)\)
=> BIT[14] + BIT[12] + BIT[8]
\(12=14 - lowbit(14)\)
每次往左走\(lowbit(x)\)
void modify(int p, int v) {
for(; p <= n; p += p & -p)
BIT[p] += v;
}
ll query(int q) {
ll ret = 0;
for(; q > 0; q -= q & -q)
ret += BIT[q];
return ret;
}
超好寫 出錯機率大概是\(10^{-9}\)
因為每次都往你ㄉlowbit值走,所以每次查詢跟修改的時間複雜度都是\(O(logn)\)
空間複雜度: 開長度為\(n\)的陣列 空間複雜度\(O(n)\)
coding複雜度: \(O(1)\)