zkw線段樹解決區間rmq
阿新 • • 發佈:2018-12-24
zkw線段樹具體內容請百度統計的力量(這是他講的時候所用的ppt的名字)
今天我們就來完整的寫一個zkw線段樹。
正如他在ppt裡講的
*差分是化絕對為相對的重要手段 *標記永久化後就是值,只不過這種值是相對的 *計算答案時可以利用從節點到根部的資訊 在zkw樹中,每個節點存的都不是最大值M[x], 而是M[x] - M[x<<1](這裡M[x<<1]就是他父親存在最大值)。這樣有什麼用呢?你可以試一下從某個結點開始,走到根,你會發現一路上的和加起來,就是這裡存在最大值。這樣,單點Query就解決了。單點更新也比較簡單,就是一點一點從葉子結點處開始修改,維護這棵樹作為差分樹的性質。只這樣看的話,並不比普通的線段樹有多大優勢。至於區間查詢,由於每個點存的內容都和它的兒子沒有關係,所以某種意義上我們可以直接找到區間的邊界點,然後開始一層一層向上累加去個最大or最小就可以了。但是這個找邊界點的過程還會浪費點地方。不如直接就從邊界的葉子節點處一層一層向上累加,每到一層,都注意去最大or最小,來維護答案。
然後就是他的ppt中的錯誤,第一處在add函式裡,它沒有上浮到根節點,這樣的話單就下次查詢來說,並不會造成什麼(你可以試試,這是從某點一直到根的路徑中的sum仍為這個點存的Max),但是,如果有了另一次更新,這時,就會出現問題(出個資料模擬一下就可以)。所以在add函式的尾部還要加一部上浮到根節點的語句。
第二處在Query函式中,如果這時查詢的不是區間是點,這時會有l=r,進一次迴圈,你發現l=r=0,這時,很明顯,函式最後的那步上浮到根節點的迴圈是出不去了。
第三處仍在Query函式中,那就是這時的查詢不再是開區間了,而是閉區間。比如區間的左節點是某個點的右兒子,那麼這時的開區間頭便是某個點的左兒子。這時按照zkw的慣例,下次的開區間節點就是他的父親了,但是由於他的父親的兒子中有區間內的點,從而這個父親也算區間內的點,那麼這時會出現了矛盾,也就是說,本來要經歷的點卻沒有經歷。這樣肯定就有問題了,所以我們只能閉區間查詢,這樣才不會發生上浮一層之後,點與區間的位置關係發生改變。
好了,這次的總結就差不多了。這裡是cf上的一個題目,考的就是這個。底下附上我的程式碼,可以看一下細節部分。
#include <bits/stdc++.h>
#define up(i, lower, upper) for(int i = lower; i < upper; i++)
#define down(i, lower, upper) for(int i = upper-1; i >= lower; i--)
using namespace std;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef vector<int> vi;
typedef vector<pii> vpii;
typedef __int64 ll;
typedef unsigned __int64 ull;
const double pi = acos(-1);
const double eps = 1.0e-9;
template<class T>
inline char read(T &n){
T x = 0, tmp = 1; char c = getchar();
while((c < '0' || c > '9') && c != '-' && c != EOF) c = getchar();
if(c == '-') c = getchar(), tmp = -1;
while(c >= '0' && c <= '9') x *= 10, x += (c - '0'),c = getchar();
n = x*tmp;
return c;
}
template <class T>
inline void write(T n) {
if(n < 0) {
putchar('-');
n = -n;
}
int len = 0,data[20];
while(n) {
data[len++] = n%10;
n /= 10;
}
if(!len) data[len++] = 0;
while(len--) putchar(data[len]+48);
}
//---------------------------------------------------------
struct SegTree {
ll tree[550000];
int m;
void build(int len) {
m = 1;
memset(tree, 0, sizeof tree);
while(m-1 <= len) m*=2;
up(i, 1, len+1) read(tree[i+m]);
down(i, 1, m) tree[i] = min(tree[(i<<1)], tree[(i<<1)+1]);
down(i, 1, len+m+1) tree[i] = tree[i] - tree[i>>1];
}
void update(int l, int r, int val) {
ll tmp;
for(l += m-1, r += m+1; l^r^1; l>>=1, r>>=1) {
if(~l&1) tree[l^1]+=val;
if(r&1) tree[r^1]+=val;
tmp = min(tree[l], tree[l^1]), tree[l] -= tmp, tree[l^1] -= tmp, tree[l>>1] += tmp;
tmp = min(tree[r], tree[r^1]), tree[r] -= tmp, tree[r^1] -= tmp, tree[r>>1] += tmp;
}
for (;l!=1;l>>=1)
tmp = min(tree[l],tree[l^1]), tree[l] -= tmp, tree[l^1] -= tmp, tree[l>>1] += tmp;
}
ll query(int l, int r) {
ll lAns = 0, rAns = 0;
l+=m, r+=m;
if(l != r) {
for(; l^r^1; l>>=1, r>>=1) {
lAns += tree[l], rAns += tree[r];
if(~l&1) lAns = min(lAns, tree[l^1]);
if(r&1) rAns = min(rAns, tree[r^1]);
}
}
ll ans = min(lAns+tree[l], rAns+tree[r]);
while(l > 1) ans += tree[l>>=1];
return ans;
}
};
SegTree a;
int main() {
int len, n, m, l, r;
read(len);
a.build(len);
read(n);
while(n--) {
read(l);
if(read(r) != '\n') {
read(m);
if(l > r) a.update(l+1, len, m), a.update(1, r+1, m);
else a.update(l+1, r+1, m);
}
else {
if(l > r) write(min(a.query(l+1, len), a.query(1, r+1)));
else write(a.query(l+1, r+1));
puts("");
}
}
return 0;
}