我會對比懶更新操作正常更新操作以及如何改變查詢操作。
在正常的單個更新操作中,您更新樹的根,然後遞歸地僅更新樹的所需部分(從而爲您提供O(log(n))
速度)。如果你將嘗試使用相同的邏輯爲一系列的更新,你可以看到它如何惡化到O(n)
(考慮非常廣泛的範圍看,你將大部分需要更新樹的兩部分)。
所以爲了克服這個問題O(n)
的想法是隻有當你真的需要它時才更新樹(查詢/更新以前更新過的段,從而使你的更新懶惰)。所以這裏是它是如何工作的:
- 樹的創建保持完全相同。唯一的區別是您還創建了一個數組,其中包含有關潛在更新的信息。
- 在更新樹的節點,你也檢查它是否需要更新(從以前的更新操作),如果它是 - 你更新它,紀念孩子在將來被更新和取消標記節點(懶惰)
- 在查詢樹,你也檢查節點是否需要更新,如果是更新,標誌着它的兒童和事後取消標記它。
以下是更新和查詢(解決最大範圍查詢)的示例。對於full code - check this article。
void update_tree(int node, int a, int b, int i, int j, int value) {
if(lazy[node] != 0) { // This node needs to be updated
tree[node] += lazy[node]; // Update it
if(a != b) {
lazy[node*2] += lazy[node]; // Mark child as lazy
lazy[node*2+1] += lazy[node]; // Mark child as lazy
}
lazy[node] = 0; // Reset it
}
if(a > b || a > j || b < i) // Current segment is not within range [i, j]
return;
if(a >= i && b <= j) { // Segment is fully within range
tree[node] += value;
if(a != b) { // Not leaf node
lazy[node*2] += value;
lazy[node*2+1] += value;
}
return;
}
update_tree(node*2, a, (a+b)/2, i, j, value); // Updating left child
update_tree(1+node*2, 1+(a+b)/2, b, i, j, value); // Updating right child
tree[node] = max(tree[node*2], tree[node*2+1]); // Updating root with max value
}
和查詢:
int query_tree(int node, int a, int b, int i, int j) {
if(a > b || a > j || b < i) return -inf; // Out of range
if(lazy[node] != 0) { // This node needs to be updated
tree[node] += lazy[node]; // Update it
if(a != b) {
lazy[node*2] += lazy[node]; // Mark child as lazy
lazy[node*2+1] += lazy[node]; // Mark child as lazy
}
lazy[node] = 0; // Reset it
}
if(a >= i && b <= j) // Current segment is totally within range [i, j]
return tree[node];
return max(query_tree(node*2, a, (a+b)/2, i, j), query_tree(1+node*2, 1+(a+b)/2, b, i, j));
}
當你說「說你要更新的範圍[0,15],那麼你更新的節點[0,15],並在節點設置一個標誌那就是說它的子節點是要更新的「如何更新[0,15]而不更新子節點。我沒有得到這個概念。這就是爲什麼我在這裏問。也可以使用自頂向下的遞歸函數來完成嗎?在update_tree函數中的 – dejavu