Vue Element-ui 之 el-table自動滾動
-
-
演算法訓練營6.4
-
-
簡介:
-
名稱:樹套樹
-
本質:一個節點為另一種樹形結構(也可以是自己)的樹形結構。
-
一些abstract:我們用平衡樹實現過查詢過一棵樹中的第k小,但是沒有做到查詢某一個區間的第k小,更不用說帶動態修改的區間第k小了。如果不要求線上處理,cdq可以解決動態區間第k小,但是線上的話需要用樹套樹。
樹套樹指在一個樹形資料結構上,每個節點不再是一個節點,而是另一種樹形結構,最常見的樹套樹有線段樹套線段樹、線段樹套平衡樹、樹狀陣列套平衡樹,嘗試做到兩個資料結構的功能的並集。
以線段樹套平衡樹為例:線段樹可以用來點、區間更新以及查詢;平衡樹可以用來查詢第k小、排名、前驅和後繼。我們用線段樹維護區間,再用平衡樹維護區間中的動態修改。先構造出線段樹,每個線段樹的節點除了記錄左右邊界,還用一棵平衡樹維護這一個區間中的所有數,具體見例子,
-
-
例題:
-
(P3380/bzoj3196/Tyvj1730)要求維護一個有序數列,需要支援:
-
查詢k在區間內的排名
-
查詢區間內排名為k的值
-
修改某一個位置上的數值
-
查詢k在區間內的前驅(最大的嚴格小於x的數,若不存在輸出-2147483647)
-
查詢k在區間內的後繼(最小的嚴格大於x的數,若不存在輸出2147483647)
區間操作和動態更新,所以可以用線段樹+平衡樹解決。
-
演算法設計:
為線段樹的每個節點都開闢一棵和區間大小相同的平衡樹,平衡樹一般用Treap或伸展樹。線段樹的每一層區間包含的元素個數都為n(因為每一層都是整個區間拆開的結果,然後每個節點都有一棵區間長度大小的平衡樹,所以相當於又合了起來)。至多有logn層,於是所有的平衡樹的節點總數是nlogn的。此樹套樹如圖所示:
-
演算法實現:
-
建立線段樹和平衡樹。
先建立線段樹,然後每個節點的區間資料都插入該節點對應的平衡樹中。
void build(int x, int l, int r) {
a[x].root = 0;
for (int i = l; i <= r; i++) {
a[x].insert(a[x].root, p[i]);
}
if (l == r) return;
int mid = l + r >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
} -
查詢k在[ql, qr]之間的排名(最後別忘了+1):
線上段樹中執行區間查詢,把每個線段樹節點中的平衡樹中的排名加起來再加1就是最終排名。
int queryrank(int x, int l, int r, int ql, int qr, int k) { // 當前節點為x,其左右界限為[l, r],查詢區間為[ql, qr],查詢k的排名
if(l > qr || r < ql) return 0; // 不相交
if (ql <= l && r <= qr) { // 完全被查詢包括
return a[x].rank(a[x].root, k); // 拿到[l, r]中比k小的個數
}
int ans = 0, mid = l + r >> 1;
ans += queryrank(x << 1, l, mid, ql, qr, k);
ans += queryrank(x << 1 | 1, mid + 1, r, ql, qr, k);
return ans;
}線段樹查詢最多O(logn)層,平衡樹查詢最多O(logn)層,所以時間複雜度是O(loglog)的。
-
查詢[ql, qr]區間排名為k的值。
區間內的元素是無序的,所以不能按區間查詢排名。而用值進行二分搜尋(初始l和r分別是總共的極小和極大值),每次查詢這個值的排名,看看和k比較一下。
int queryval(int ql, int qr, int k) {
int l = min_val, r = max_val, ans = -1, rank;
while(l <= r) {
mid = l + r >> 1;
rank = queryrank(1, 1, n, ql, qr, mid);
if (rank + 1 <= k) { // 如果排名已經為k,則還可以變大,l也是要mid + 1
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ans;
}複雜度為O(lognlognlog(max-min))。
-
點更新:
修改pos位置上的數為k。與線段樹的點更新差不多,外加要更新每個節點對應的平衡樹,最後修改p[pos] = k。
void modify(int x, int l, int r, int pos, int k) {
if (pos < l || pos > r) return; // 不在這個範圍內
a[x].remove(a[x].root, p[pos]); // 先刪除這個值
a[x].insert(a[x].root, k); // 再插入新值
if (l == r) return;
int mid = l + r >> 1;
modify(x << 1, l, mid, pos, k);
modify(x << 1 | 1, mid + 1, r, pos, k);
}線段樹中查詢O(logn)層,刪除和插入的複雜度為O(logn),總複雜度為O(lognlogn)。
-
查詢k在[ql, qr]區間的前驅:
若查詢區間和當前節點的無交集,返回-inf;若查詢區間覆蓋了當前節點,則在當前節點平衡樹中查詢前驅;否則在左右子樹中搜索,分別求前驅。
int querypre(int x, int l, int r, int ql, int qr, int k) {
if (l > qr || r < ql) return -inf; // 不相交
if (ql <= l && r <= qr) return a[x].pre(a[x].root, k); // 完全覆蓋在整個平衡樹中查詢前驅
int mid = l + r >> 1;
int ans = -inf;
ans = max(ans, querypre(x << 1, l, mid, ql, qr, k));
ans = max(ans, querypre(x << 1 | 1, mid + 1, r, ql, qr, k));
return ans;
}線段樹一共O(logn)層,查詢複雜度也是O(logn),所以總時間複雜度為O(lognlogn)。
-
查詢k在[ql, qr]區間的後繼:
基本同上:
int querynxt(int x, int l, int r, int ql, int qr, int k) {
if (l > qr || r < ql) return inf; // 不相交
if (ql <= l && r <= qr) return a[x].nxt(a[x].root, k); // 完全覆蓋在整個平衡樹中查詢後繼
int mid = l + r >> 1;
int ans = inf;
ans = min(ans, querynxt(x << 1, l, mid, ql, qr, k));
ans = min(ans, querynxt(x << 1 | 1, mid + 1, r, ql, qr, k));
return ans;
}總時間複雜度O(lognlogn)。
-
-
-
(POJ1195)矩形區域查詢。二維的點更新和區間查詢。因為只有點更新,所以之前用二維樹狀陣列解決過,這裡用線段樹套線段樹解決。
線段樹一共有O(n)個節點,每個節點又有一個O(n)節點的線段樹,所以空間複雜度為O(n^2)的。查詢、更新操作總時間複雜度為O(lognlogn)的。
-
資料結構定義:建立一維線段樹和二維線段樹節點
struct node_y { // 第二維線段樹節點,用來維護縱座標的和
int l, r; // 縱座標的區間
int sum; // 和值
};
struct node_x { // 第一維線段樹節點,維護二維區間的和
int l, r; // 橫座標的區間
node_y s[maxn << 2]; // 第二維線段樹
}tr[maxn << 2]; -
建立樹套樹:不同於一維的,需要多一個引數,代表為哪個一維線段樹節點建立二維線段樹
void build_y(int i, int l, int r, int k) { // i為二維節點,代表[l, r]區間,k為一維線段樹節點
tr[k].s[i].l = l;
tr[k].s[i].r = r;
tr[k].s[i].sum = 0; // 原題初始化就全是0
if (l == r) return;
int mid = l + r >> 1;
build_y(i << 1, l, mid, k);
build_y(i << 1 | 1, mid + 1, r, k);
}
void build_x(int i, int l1, int r1, int l2, int r2) { // i為一維線段樹節點,[l1, r1]是一維的範圍,[l2, r2]是二維的範圍,但這裡[l2, r2]只能是[1, y_max]
tr[i].l = l1;
tr[i].r = r1;
build_y(1, l2, r2, i);
if (l1 == r1) return;
int mid = l1 + r1 >> 1;
build_x(i << 1, l, mid, l2, r2);
build_x(i << 1 | 1, mid + 1, r, l2, r2);
} -
點更新:
void update_y(int i, int y, int val, int k) { // k是一維節點序號,i是二維節點序號 val是要加的值 y是要改的縱座標
tr[k].s[i].sum += val;
if (tr[k].s[i].l == tr[k].s[i].r) return;
int mid = (tr[k].s[i].l + tr[k].s[i].r) >> 1;
if (y <= mid) update_y(i << 1, y, val, k);
else update_y(i << 1 | 1, y, val, k);
}
void update_x(int k, int x, int y, int val) { // k是一維節點序號,(x, y)是座標,+val
update_y(1, y, val, k); // 對k節點的整棵樹進行點更新
if (tr[k].l == tr[k].r) return;
int mid = tr[k].l + tr[k].r >> 1;
if (x <= mid) update_x(k << 1, x, y, val);
else update_x(k << 1 | 1, x, y, val);
} -
區間查詢:
int query_y(int i, int l, int r, int k) {
if (tr[k].s[i].l == l && tr[k].s[i].r == r) return tr[k].s[i].sum;
int mid = (tr[k].s[i].l + tr[k].s[i].r) >> 1;
if (r <= mid) return query_y(i << 1, l, r, k);
else if (l > mid) return query_y(i << 1 | 1, l, r, k);
else return query_y(i << 1, l, mid, k) + query_y(i << 1 | 1, mid + 1, r, k);
}
int query_x(int k, int l1, int r1, int l2, int r2) { // 查詢區間[l1, r1][l2, r2]
if (tr[k].l == l1 && tr[k].r == r1) return query_y(1, l2, r2, k);
int mid = tr[k].l + tr[k].r >> 1;
if (r1 <= mid) return query_x(k << 1, l1, r2, l2, r2);
else if (l > mid) return query_x(i << 1 | 1, l1, r2, l2, r2);
else return query_x(k << 1, l1, r2, l2, r2) + query_x(i << 1 | 1, l1, r2, l2, r2);
}
-
-
(HDU4819)點更新 & 區間查詢(最大值和最小值)
所有二維的最小值的最小值是一維的最小值,所以可以樹套樹。
-
資料結構定義:
struct node {
int Max, Min;
}tr[maxn << 1][maxn << 1]; // 第i維就是處理i維座標 -
建樹:
void pushup_x(int i, int k) { // 1-i2-k
tr[k][i].Max = max(tr[k << 1][i].Max, tr[k << 1 | 1][i].Max);
tr[k][i].Min = min(tr[k << 1][i].Min, tr[k << 1 | 1][i].Min);
}
void pushup_y(int i, int k) { // 1-i2-k
tr[k][i].Max = max(tr[k][i << 1].Max, tr[k][i << 1 | 1].Max);
tr[k][i].Min = min(tr[k][i << 1].Min, tr[k][i << 1 | 1].Min);
}
void build_y(int i, int k, int l, int r, int flag) { // i第二維座標;k第一維座標;處理第二維的[l, r];flag == 1代表橫座標區間已經是一個點了,此時不用管兒子,flag == 2表示橫座標仍然是一個區間,這時要根據兒子的答案取父親的答案
int mid, val;
if (l == r) {
if (flag == 1) {
scanf("%d", &val);
tr[k][i].Max = tr[k][i].Min = val;
} else {
pushup_x(i, k);
}
return;
}
mid = (l + r) >> 1;
build_y(i << 1, k, l, mid, flag);
build_y(i << 1 | 1, k, mid + 1, r, flag);
}
void build_x(int k, int l, int r) {
if (l == r) {
build_y(1, k, 1, n, 1); // 整棵樹都要建,已經為葉子節點,flag == 1
return;
}
int mid = l + r >> 1;
// 一方面處理更小的豎著的矩形
build_x(k << 1, l,
-
-