此題題意很好懂:
給你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 ...