線段樹的一些腦殘東西例如向上回溯延遲更新的噁心東西

2021-07-16 08:25:35 字數 3313 閱讀 6683

此題題意很好懂:

給你n個數,q個操作,操作有兩種,『q a b 』是詢問a~b這段數的和,『c a b c』是把a~b這段數都加上c。

需要用到線段樹的,update:成段增減,query:區間求和

介紹lazy思想:lazy-tag思想,記錄每乙個線段樹節點的變化值,當這部分線段的一致性被破壞我們就將這個變化值傳遞給子區間,大大增加了線段樹的效率。

在此通俗的解釋我理解的lazy意思,比如現在需要對[a,b]區間值進行加c操作,那麼就從根節點[1,n]開始呼叫update函式進行操作,如果剛好執行到乙個子節點,它的節點標記為rt,這時tree[rt].l == a && tree[rt].r == b 這時我們可以一步更新此時rt節點的sum[rt]的值,sum[rt] += c * (tree[rt].r - tree[rt].l + 1),注意關鍵的時刻來了,如果此時按照常規的線段樹的update操作,這時候還應該更新rt子節點的sum值,而lazy思想恰恰是暫時不更新rt子節點的sum值,到此就return,直到下次需要用到rt子節點的值的時候才去更新,這樣避免許多可能無用的操作,從而節省時間 。

下面通過具體的**來說明之。(此處的函式名和巨集學習了小hh的**風格)

在此先介紹下**中的函式說明:

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

巨集定義左兒子lson和右兒子rson,貌似用巨集的速度要慢。

pushup(rt):通過當前節點rt把值遞迴向上更新到根節點

pushdown(rt):通過當前節點rt遞迴向下去更新rt子節點的值

rt表示當前子樹的根(root),也就是當前所在的結點

__int64 sum[n<<2],add[n<<2];

struct node

} tree[n<<2];

這裡定義資料結構sum用來儲存每個節點的子節點數值的總和,add用來記錄該節點的每個數值應該加多少

tree.l tree.r分別表示某個節點的左右區間,這裡的區間是閉區間

下面直接來介紹update函式,lazy操作主要就是用在這裡

void update(int c,int l,int r,int rt)//表示對區間[l,r]內的每個數均加c,rt是根節點

if(tree[rt].l == tree[rt].r) return;

pushdown(rt,tree[rt].r - tree[rt].l + 1);

int m = tree[rt].mid();

if(r <= m) update(c,l,r,rt<<1);

else if(l > m) update(c,l,r,rt<<1|1);

else

pushup(rt);

}if(tree[rt].l == l && r == tree[rt].r) 這裡就是用到lazy思想的關鍵時刻 正如上面說提到的,這裡首先更新該節點的sum[rt]值,然後更新該節點具體每個數值應該加多少即add[rt]的值,注意此時整個函式就執行完了,直接return,而不是還繼續向子節點繼續更新,這裡就是lazy思想,暫時不更新子節點的值。

那麼什麼時候需要更新子節點的值呢?答案是在某部分update操作的時候需要用到那部分沒有更新的節點的值的時候,這裡可能有點繞口。這時就掉用pushdown()函式更新子節點的數值。

void pushdown(int rt,int m)

}pushdown就是從當前根節點rt向下更新每個子節點的值,這段**讀者可以自己好好理解,這也是lazy的關鍵。

接著就是update操作的三個if語句了,這裡我曾經一直不理解,多虧nyf隊友的指點,藉此感謝之。

下面再解釋query函式,也就是用這個函式來求區間和

__int64 query(int l,int r,int rt)

pushdown(rt,tree[rt].r - tree[rt].l + 1);

int m = tree[rt].mid();

__int64 res = 0;

if(r <= m) res += query(l,r,rt<<1);

else if(l > m) res += query(l,r,rt<<1|1);

else

return res;

}第乙個if還是區間的判斷和前面update的一樣,到這裡就可以知道答案了,所以就直接return。

接下來的查詢就需要用到rt子節點的值了,由於我們用了lazy操作,這段的數值還沒有更新,因此我們需要呼叫pushdown函式去更新之,滿足if(add[rt])就說明還沒有更新。

ps:今天總算是對線段樹入門了。

#include #include using namespace std;

const int n = 100005;

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

__int64 sum[n<<2],add[n<<2];

struct node

} tree[n<<2];

void pushup(int rt)

void pushdown(int rt,int m)

}void build(int l,int r,int rt)

int m = tree[rt].mid();

build(lson);

build(rson);

pushup(rt);

}void update(int c,int l,int r,int rt)

if(tree[rt].l == tree[rt].r) return;

pushdown(rt,tree[rt].r - tree[rt].l + 1);

int m = tree[rt].mid();

if(r <= m) update(c,l,r,rt<<1);

else if(l > m) update(c,l,r,rt<<1|1);

else

pushup(rt);

}__int64 query(int l,int r,int rt)

pushdown(rt,tree[rt].r - tree[rt].l + 1);

int m = tree[rt].mid();

__int64 res = 0;

if(r <= m) res += query(l,r,rt<<1);

else if(l > m) res += query(l,r,rt<<1|1);

else

return res;

}int main()

else}}

return 0;

}

二叉樹的一些東西

了解完全二叉樹和滿二叉樹 滿二叉樹 除最後一層無任何子節點外,每一層上的所有結點都有兩個子結點 最後一層上的無子結點的結點 為葉子結點 也可以這樣理解,除葉子結點外的所有結點均有兩個子結點。節點數達到最大值。所有葉子結點必須在同一層上。完全二叉樹 若設二叉樹的深度為h,除第 h 層外,其它各層 1 ...

一些關於線段樹的操作記錄

由於筆者本身總是弄不清楚線段樹的各種寫法之間的差異,故寫在這裡方便看懂題解 關於可持久化 關於各種玩意兒套線段樹 關於離散化 關於卡常 關於遞迴 關於權值線段樹 1.分三種情況遞迴的 完全在左子樹,完全在右子樹,和兩邊都有的 複雜度證明 兩邊都有的情況顯然只會出現一次,而樹高為log n log n...

決策樹的一些東西,亂寫的當個總結。

有兩個非常開闊視野的文章 隨機森林主要優點 該模型能夠輸出變數的重要性程度 在對缺失資料進行估計時,隨機森林是乙個十分有效的方法,隨機森林演算法中包含了對輸入資料的重複自抽樣過程,即所謂的bootstrap抽樣。這樣一來,資料集中大約三分之一將沒有用於模型的訓練而是用於測試,這樣的資料被稱為out ...