线段树入门
線段樹
?
?
前言
什么是線段樹
線段樹,是一種二叉搜索樹。它將一段區(qū)間劃分為若干單位區(qū)間,每一個節(jié)點(diǎn)都儲存著一個區(qū)間。它功能強(qiáng)大,支持區(qū)間求和,區(qū)間最大值,區(qū)間修改,單點(diǎn)修改等操作。
線段樹的思想和分治思想很相像。
線段樹的每一個節(jié)點(diǎn)都儲存著一段區(qū)間[L…R]的信息,其中葉子節(jié)點(diǎn)L=R。它的大致思想是:將一段大區(qū)間平均地劃分成2個小區(qū)間,每一個小區(qū)間都再平均分成2個更小區(qū)間……以此類推,直到每一個區(qū)間的L等于R(這樣這個區(qū)間僅包含一個節(jié)點(diǎn)的信息,無法被劃分)。通過對這些區(qū)間進(jìn)行修改、查詢,來實(shí)現(xiàn)對大區(qū)間的修改、查詢。
這樣一來,每一次修改、查詢的時間復(fù)雜度都只為O(logN),而空間復(fù)雜度一般開4N。
但是,可以用線段樹維護(hù)的問題必須滿足區(qū)間加法,否則是不可能將大問題劃分成子問題來解決的。
?證明:空間復(fù)雜度開4N
假設(shè):N=2h???
?
?
那么layer[0] = 20,layer[1] = 21+……+layer[h] = 2h
Sum = 20+21+22+……+2h ?= ?= 2*2h-1 = 2N-1
但是這只是特殊情況,大多數(shù)情況 N = 2h+M (M<2h)
所有:2h < N < 2h+1???? 那么 ???2*2h-1 < Sum < 2*2h+1-1
那我們就取大的,開2*2h+1-1 = 4*2h-1 < 4N
?
?
什么是區(qū)間加法
一個問題滿足區(qū)間加法,僅當(dāng)對于區(qū)間[L,R]的問題的答案可以由[L,M]和[M+1,R]的答案合并得到。經(jīng)典的區(qū)間加法問題有:
滿足區(qū)間加法的例子:
1,區(qū)間和??? ????總區(qū)間和 = 左區(qū)間和+右區(qū)間和
2,最大公因數(shù) ???總GCD ?= GCD(左區(qū)間GCD,右區(qū)間GCD)
3,最大值 ???????總最大值 = MAX(左區(qū)間MAX,右區(qū)間MAX)
不滿足區(qū)間加法的例子:
1,區(qū)間眾數(shù) ??????????總區(qū)間總數(shù)無法根據(jù)左右區(qū)間總數(shù)求出來
2,最長遞增序列長度 ???總區(qū)間最長遞增長度無法直接由左右區(qū)間最長遞增長度相加而得
線段樹的原理及實(shí)現(xiàn)
注意:如果我沒有特別申明的話,這里的詢問全部都是區(qū)間求和
線段樹主要是把一段大區(qū)間平均地劃分成兩段小區(qū)間進(jìn)行維護(hù),再用小區(qū)間的值來更新大區(qū)間。這樣既能保證正確性,又能使時間保持在log級別(因?yàn)檫@棵線段樹是平衡的)。也就是說,一個[L…R]的區(qū)間會被劃分成[L…(L+R)/2]和[(L+R)/2+1…R]這兩個小區(qū)間進(jìn)行維護(hù),直到L=R。
下圖就是一棵[1, 5]的線段樹的分解過程(相同顏色的節(jié)點(diǎn)在同一層)
?
?
可以發(fā)現(xiàn),這棵線段樹的最大深度不超過[log2(n?1)]+2(其中[x]表示對x進(jìn)行下取整)
a.儲存方式
通常用的都是堆式儲存法,即編號為n的節(jié)點(diǎn)的左兒子編號為n?2,右兒子編號為n?2+1,父節(jié)點(diǎn)編號為n/2用位運(yùn)算優(yōu)化一下,以上的節(jié)點(diǎn)編號就變成:
左兒子:n << 1 右兒子:n << 1|1 父節(jié)點(diǎn):n >> 1
通常,每一個線段樹上的節(jié)點(diǎn)儲存的都是這幾個變量:區(qū)間左邊界,區(qū)間右邊界,區(qū)間的答案(這里為區(qū)間元素之和)和懶惰標(biāo)記
下面是線段樹的定義:
const int maxh = 4*N; struct node { int l; //左邊界 int r; // 右邊界 int val, lazy; //節(jié)點(diǎn)所維護(hù)的區(qū)間[l, r]的權(quán)值, 懶惰標(biāo)記 }tree[maxh];//N為總節(jié)點(diǎn)數(shù) int val[maxh]; //原數(shù)組
b.初始化
常見的做法是遍歷整棵線段樹,給每一個節(jié)點(diǎn)賦值,注意要遞歸到線段樹的葉節(jié)點(diǎn)才結(jié)束。
void pushup(int n) { t[n].val = t[2*n].val+t[2*n+1].val; //總區(qū)間和 = 左區(qū)間和+右區(qū)間和 } void build(int n,int l,int r) { t[n].l = l; //記錄維護(hù)的原數(shù)組的左端點(diǎn) t[n].r = r; //記錄維護(hù)的右端點(diǎn) t[n].lazy = 0; //標(biāo)記下懶惰數(shù)組 if(l==r){ //l==r,表示葉子節(jié)點(diǎn), t[n].val = val[l]; //因?yàn)閘==r,那么這個節(jié)點(diǎn)維護(hù)的值就是原數(shù)組val[l]的值 return; } int mid = (l+r)>>1; build(n*2,l,mid); //遞歸建左子樹 build(n*2+1,mid+1,r); //遞歸建右子樹 pushup(n); //求該點(diǎn)的權(quán)值 }
?
?
c.單點(diǎn)修改
時間復(fù)雜度:O(log2(N))
線段樹的高度接近log2(N):所以更新經(jīng)過的節(jié)點(diǎn)數(shù)接近log2(N)?
當(dāng)我們要把下標(biāo)為idx的元素修改(加減乘除、賦值運(yùn)算等),這里我們加上一個值C,可以遞歸向下找到葉結(jié)點(diǎn),再向上更新,左節(jié)點(diǎn)維護(hù)的范圍是[l, mid],右節(jié)點(diǎn)維護(hù)的范圍是[mid+1, r]。
如果:idx<=mid,那么val[idx]就由左節(jié)點(diǎn)維護(hù),所有走左節(jié)點(diǎn)
如果:mid<idx,那么val[idx]由右節(jié)點(diǎn)維護(hù),所有走右節(jié)點(diǎn)
最后直到l==r的時候找到葉子節(jié)點(diǎn),這個節(jié)點(diǎn)維護(hù)的范圍就是[idx, idx]即val[idx]這個元素
修改這個葉子節(jié)點(diǎn)的權(quán)值,最后回溯向上修改沿途的節(jié)點(diǎn)權(quán)值
void updateOne(int n,int idx,int C) { int l = t[n].l;//左端點(diǎn) int r = t[n].r;//右端點(diǎn) if(l==r) //l==r,到了葉子節(jié)點(diǎn) { t[n].val += C; //更新權(quán)值 return; } int mid = (l+r)>>1; if(idx<=mid) //val[idx]由左節(jié)點(diǎn)維護(hù) updateOne(n*2,idx,C); else //val[idx]由右節(jié)點(diǎn)維護(hù) updateOne(n*2+1,idx,C); pushup(n); //向上更新 }
?
? ?
?
模擬更新[2, 5],從n=1開始
第一輪:節(jié)點(diǎn)2:[1, 3]與待更新范圍有交集要走,節(jié)點(diǎn)3:[4, 5]與待更新范圍有交集要走
第二輪:節(jié)點(diǎn)4:[1, 2]有交集要走,節(jié)點(diǎn)5:[3, 3]有交集要走,節(jié)點(diǎn)[4, 5]被更新范圍包括直接更新,lazy+=1不向下走。
第三輪:節(jié)點(diǎn)1:[1, 1]沒有交集排除,節(jié)點(diǎn)9:[2, 2]被包括更新,節(jié)點(diǎn)3:[3, 3]被包括更新
結(jié)束!
?
時間復(fù)雜度:O(log2N)
定理:n>=3時,一個[1,n]的線段樹可以將[1,n]的任意子區(qū)間[L,R]分解為不超過2|log2(N-1)| 向下取整子區(qū)間。
?
d.區(qū)間修改
待更新區(qū)間為[L, R],左節(jié)點(diǎn)維護(hù)的區(qū)間[l, mid], 右節(jié)點(diǎn)維護(hù)的區(qū)間[mid+1, r]
L<=mid,左節(jié)點(diǎn)維護(hù)[l, mid]與[L, R]有交集要走
mid<R,?? 右節(jié)點(diǎn)維護(hù)[mid+1, r]與[L, R]有交集要走
如果[l, r]被[L, R],直接更新,更新一下lazy標(biāo)記,不往下走
遞歸進(jìn)行直到更新結(jié)束
?
這里就要引入一樣新的神奇的東西——懶惰標(biāo)記!
懶惰標(biāo)記
標(biāo)記的含義:本區(qū)間已經(jīng)被更新過了,但是子區(qū)間卻沒有被更新過,但是子結(jié)點(diǎn)暫時不更新,使用到時再更新,懶惰標(biāo)記用于記錄更新的信息(如果區(qū)間求和只用記錄加了多少,而區(qū)間加減乘除等多種操作的問題則要記錄進(jìn)行的是哪一種操作,可能記錄的信息會復(fù)雜一些)
這里再引入兩個很重要的東西:相對標(biāo)記和絕對標(biāo)記。
相對標(biāo)記&絕對標(biāo)記
相對標(biāo)記指的是可以共存的標(biāo)記,且打標(biāo)記的順序與答案無關(guān),即標(biāo)記可以疊加。 比如說給一段區(qū)間中的所有數(shù)字都+a,我們就可以把標(biāo)記疊加一下,比如上一次打了一個+1的標(biāo)記,這一次要給這一段區(qū)間+2,那么就把+1的標(biāo)記變成+3。
絕對標(biāo)記是指不可以共存的標(biāo)記,每一次都要先把標(biāo)記下傳,再給當(dāng)前節(jié)點(diǎn)打上新的標(biāo)記。這些標(biāo)記不能改變次序,否則會出錯。 比如說給一段區(qū)間的數(shù)字重新賦值,或是給一段區(qū)間進(jìn)行多種操作。
有了懶惰標(biāo)記這種神奇的東西,我們區(qū)間修改時就可以偷一下懶,先修改當(dāng)前節(jié)點(diǎn),然后直接把修改信息掛在節(jié)點(diǎn)上就可以了!
如下面這棵線段樹,當(dāng)我們要修改區(qū)間[1…4],將元素賦值為1時,我們可以先找到所有的整個區(qū)間都要被修改的節(jié)點(diǎn),顯然是儲存區(qū)間[1…3]和[4…4]的這兩個節(jié)點(diǎn)。我們就可以先把[1…3]的sum改為3((3?1+1)?1=3,把[4…4]的sum改1((1?1+1)?1=1 然后給它們打上值為1的懶惰標(biāo)記,然后就可以了。
比如下面這顆線段樹,當(dāng)我們修改[1, 4]區(qū)間都+1時我們先找到要修改的節(jié)點(diǎn),就是t[2]和t[6],之后修改權(quán)值,打上標(biāo)記
t[2]={???????????????????????????????????????????? t[6] = {
????? l = 1;??????????????????????????????????????? ????????????????? l = 4;???
????? r = 3;???????????????????????????????????????????????????????? r = 4;??
????? val = 9;????????????????????????????????????????????????????? val = 5;
????? lazy = 1;??????????????????????????????????????????????????? lazy = 1;???
}?????????????????????????????????????????????????????????? }????
?
?
?
這樣一來,我們每一次修改區(qū)間時只要找到目標(biāo)區(qū)間就可以了,不用再向下遞歸到葉節(jié)點(diǎn)。
下面是區(qū)間[L, R]+C的代碼
void pushdown(int n) { int l = t[n].l; int r = t[n].r; if(l==r)//葉子節(jié)點(diǎn)沒有子結(jié)點(diǎn) return; if(t[n].lazy)//懶惰標(biāo)記不為0才能向下更新 { int mid = (l+r)/2; t[2*n].val += t[n].lazy*(mid-l+1);//更新左節(jié)點(diǎn)權(quán)值 t[2*n+1].val += t[n].lazy*(r-mid);//更新右節(jié)點(diǎn)權(quán)值 t[2*n].lazy += t[n].lazy; //更新左節(jié)點(diǎn)標(biāo)記 t[2*n+1].lazy += t[n].lazy; //更新右節(jié)點(diǎn)標(biāo)記 t[n].lazy = 0; //清除標(biāo)記 } } void updateRange(int n,int L,int R,int C) { int l = t[n].l; int r = t[n].r; if(L<=l && r<=R) //待更新區(qū)間為[L, R],而[l, r]是[L, R]的子集所以更新 { t[n].val += (r-l+1)*C; //更新權(quán)值 t[n].lazy += C; //更新標(biāo)記 return; } pushdown(n); //向下更新 int mid = (l+r)>>1; if(L<=mid) //左節(jié)點(diǎn)維護(hù)的區(qū)間與[L, R]有交集 updateRange(2*n,L,R,C); if(R>mid) //右節(jié)點(diǎn)維護(hù)的區(qū)間與[L, R]有交集 updateRange(2*n+1,L,R,C); pushup(n); //向上更新 }
?
e.區(qū)間查詢
和區(qū)間修改一樣的道理,這里是直接返回結(jié)果,區(qū)間修改是修改權(quán)值
int query(int n,int L,int R) { int l = t[n].l; int r = t[n].r; if(L<=l && r<=R) //被包括直接返回 return t[n].val; pushdown(n); //向下更新 int ans = 0; //保持答案 int mid = (l+r)>>1; if(L<=mid) //查詢區(qū)間與左節(jié)點(diǎn)維護(hù)區(qū)間有交集 ans += query(2*n,L,R);//加上左節(jié)點(diǎn)交集區(qū)間的答案 if(R>mid) ans += query(2*n+1,L,R);//加上右節(jié)點(diǎn)交集區(qū)間的答案 return ans; }
?
模板:
#include <iostream> using namespace std; const int N = 1e5+10; const int maxh = 4*N; struct node { int l; //左邊界 int r; // 右邊界 int val, lazy; //節(jié)點(diǎn)所維護(hù)的區(qū)間[l, r]的權(quán)值, 懶惰標(biāo)記 }t[maxh];//N為總節(jié)點(diǎn)數(shù) int val[maxh]; //原數(shù)組 void pushup(int n) { t[n].val = t[2*n].val+t[2*n+1].val; //總區(qū)間和 = 左區(qū)間和+右區(qū)間和 } void build(int n,int l,int r) { t[n].l = l; //記錄維護(hù)的原數(shù)組的左端點(diǎn) t[n].r = r; //記錄維護(hù)的右端點(diǎn) t[n].lazy = 0; //標(biāo)記下懶惰數(shù)組 if(l==r){ //l==r,表示葉子節(jié)點(diǎn), t[n].val = val[l]; //因?yàn)閘==r,那么這個節(jié)點(diǎn)維護(hù)的值就是原數(shù)組val[l]的值 return; } int mid = (l+r)>>1; build(n*2,l,mid); //遞歸建左子樹 build(n*2+1,mid+1,r); //遞歸建左子樹 pushup(n); //求該點(diǎn)的權(quán)值 } void updateOne(int n,int idx,int C) { int l = t[n].l;//左端點(diǎn) int r = t[n].r;//右端點(diǎn) if(l==r) //l==r,到了葉子節(jié)點(diǎn) { t[n].val += C; //更新權(quán)值 return; } int mid = (l+r)>>1; if(idx<=mid) //val[idx]由左節(jié)點(diǎn)維護(hù) updateOne(n*2,idx,C); else //val[idx]由右節(jié)點(diǎn)維護(hù) updateOne(n*2+1,idx,C); pushup(n); //向上更新 } void pushdown(int n) { int l = t[n].l; int r = t[n].r; if(l==r)//葉子節(jié)點(diǎn)沒有子結(jié)點(diǎn) return; if(t[n].lazy)//懶惰標(biāo)記不為0才能向下更新 { int mid = (l+r)/2; t[2*n].val += t[n].lazy*(mid-l+1);//更新左節(jié)點(diǎn)權(quán)值 t[2*n+1].val += t[n].lazy*(r-mid);//更新右節(jié)點(diǎn)權(quán)值 t[2*n].lazy += t[n].lazy; //更新左節(jié)點(diǎn)標(biāo)記 t[2*n+1].lazy += t[n].lazy; //更新右節(jié)點(diǎn)標(biāo)記 t[n].lazy = 0; //清除標(biāo)記 } } int query(int n,int L,int R) { int l = t[n].l; int r = t[n].r; if(L<=l && r<=R) //被包括直接返回 return t[n].val; pushdown(n); //向下更新 int ans = 0; //保持答案 int mid = (l+r)>>1; if(L<=mid) //查詢區(qū)間與左節(jié)點(diǎn)維護(hù)區(qū)間有交集 ans += query(2*n,L,R);//加上左節(jié)點(diǎn)交集區(qū)間的答案 if(R>mid) ans += query(2*n+1,L,R);//加上右節(jié)點(diǎn)交集區(qū)間的答案 return ans; } void updateRange(int n,int L,int R,int C) { int l = t[n].l; int r = t[n].r; if(L<=l && r<=R) //待更新區(qū)間為[L, R],而[l, r]是[L, R]的子集所以更新 { t[n].val += (r-l+1)*C; //更新權(quán)值 t[n].lazy += C; //更新標(biāo)記 return; } pushdown(n); //向下更新 int mid = (l+r)>>1; if(L<=mid) //左節(jié)點(diǎn)維護(hù)的區(qū)間與[L, R]有交集 updateRange(2*n,L,R,C); if(R>mid) //右節(jié)點(diǎn)維護(hù)的區(qū)間與[L, R]有交集 updateRange(2*n+1,L,R,C); pushup(n); //向上更新 } int main() { return 0; }
?
?
?
例題
HDU1166
https://vjudge.net/problem/HDU-1166
單點(diǎn)更新+區(qū)間查詢
?
Input
第一行一個整數(shù)T,表示有T組數(shù)據(jù)。
每組數(shù)據(jù)第一行一個正整數(shù)N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數(shù),第i個正整數(shù)ai代表第i個工兵營地里開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,i和j為正整數(shù),表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數(shù),表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數(shù),i<=j,表示詢問第i到第j個營地的總?cè)藬?shù);
(4)End 表示結(jié)束,這條命令在每組數(shù)據(jù)最后出現(xiàn);
每組數(shù)據(jù)最多有40000條命令
Output
對第i組數(shù)據(jù),首先輸出“Case i:”和回車,
對于每個Query詢問,輸出一個整數(shù)并回車,表示詢問的段中的總?cè)藬?shù),這個數(shù)保持在int以內(nèi)。
#include <iostream> using namespace std; const int N = 5e5+10; const int maxh = 4*N; struct node { int l; //左邊界 int r; // 右邊界 int val; //節(jié)點(diǎn)所維護(hù)的區(qū)間[l, r]的權(quán)值 }t[maxh];//N為總節(jié)點(diǎn)數(shù) int val[maxh]; //原數(shù)組 void pushup(int n) {t[n].val = t[2*n].val+t[2*n+1].val; //總區(qū)間和 = 左區(qū)間和+右區(qū)間和 } void build(int n,int l,int r) {t[n].l = l; //記錄維護(hù)的原數(shù)組的左端點(diǎn) t[n].r = r; //記錄維護(hù)的右端點(diǎn) if(l==r){ //l==r,表示葉子節(jié)點(diǎn),t[n].val = val[l]; //因?yàn)閘==r,那么這個節(jié)點(diǎn)維護(hù)的值就是原數(shù)組val[l]的值 return;} int mid = (l+r)>>1;build(n*2,l,mid); //遞歸建左子樹 build(n*2+1,mid+1,r); //遞歸建左子樹pushup(n); //求該點(diǎn)的權(quán)值 } void updateOne(int n,int idx,int C) {int l = t[n].l;//左端點(diǎn) int r = t[n].r;//右端點(diǎn) if(l==r) //l==r,到了葉子節(jié)點(diǎn) {t[n].val += C; //更新權(quán)值 return; } int mid = (l+r)>>1;if(idx<=mid) //val[idx]由左節(jié)點(diǎn)維護(hù) updateOne(n*2,idx,C);else //val[idx]由右節(jié)點(diǎn)維護(hù) updateOne(n*2+1,idx,C);pushup(n); //向上更新 } int query(int n,int L,int R) {int l = t[n].l;int r = t[n].r;if(L<=l && r<=R) //被包括直接返回 return t[n].val;int ans = 0; //保持答案 int mid = (l+r)>>1;if(L<=mid) //查詢區(qū)間與左節(jié)點(diǎn)維護(hù)區(qū)間有交集 ans += query(2*n,L,R);//加上左節(jié)點(diǎn)交集區(qū)間的答案 if(R>mid)ans += query(2*n+1,L,R);//加上右節(jié)點(diǎn)交集區(qū)間的答案 return ans; } int main() {int T;scanf("%d", &T);for(int cnt = 1;cnt <= T;cnt ++){int N;char op[10];printf("Case %d:\n", cnt);scanf("%d", &N);for(int i = 1;i <= N;i ++)scanf("%d", &val[i]); //數(shù)組的權(quán)值 build(1,1,N); //建樹 while(scanf("%s", op)) {if(op[0]=='E') //END結(jié)束 break;int i, j;scanf("%d%d", &i, &j);if(op[0]=='A') //ADD,第i個營地+j人 updateOne(1,i,j);else if(op[0]=='S') //ADD,第i個營地-j人updateOne(1,i,-j);elseprintf("%d\n", query(1,i,j));//查詢i--j營地一共有多少人 }}return 0; }
?
?
?
HDU1754
https://vjudge.net/problem/HDU-1754
Input
多組測試,EOF結(jié)束
第一行,有兩個正整數(shù) N 和 M ( 0<N<=200000,0<M<5000 ),分別代表學(xué)生的數(shù)目和操作的數(shù)目。?
學(xué)生ID編號分別從1編到N。?
第二行包含N個整數(shù),代表這N個學(xué)生的初始成績,其中第i個數(shù)代表ID為i的學(xué)生的成績。?
接下來有M行。每一行有一個字符 C (只取'Q'或'U') ,和兩個正整數(shù)A,B。?
當(dāng)C為'Q'的時候,表示這是一條詢問操作,它詢問ID從A到B(包括A,B)的學(xué)生當(dāng)中,成績最高的是多少。?
當(dāng)C為'U'的時候,表示這是一條更新操作,要求把ID為A的學(xué)生的成績更改為B。?
?
Output
對于每一次詢問操作,在一行里面輸出最高成績。
?
#include <iostream> #include <cmath> using namespace std; const int N = 2e5+10; const int maxh = 4*N; struct node { int l; //左邊界 int r; // 右邊界 int val; //節(jié)點(diǎn)所維護(hù)的區(qū)間[l, r]的權(quán)值 }t[maxh];//N為總節(jié)點(diǎn)數(shù) int val[maxh]; //原數(shù)組 void pushup(int n) { t[n].val = max(t[2*n].val, t[2*n+1].val); //總區(qū)間和 = 左區(qū)間和+右區(qū)間和 } void build(int n,int l,int r) { t[n].l = l; //記錄維護(hù)的原數(shù)組的左端點(diǎn) t[n].r = r; //記錄維護(hù)的右端點(diǎn) if(l==r){ //l==r,表示葉子節(jié)點(diǎn), t[n].val = val[l]; //因?yàn)閘==r,那么這個節(jié)點(diǎn)維護(hù)的值就是原數(shù)組val[l]的值 return; } int mid = (l+r)>>1; build(n*2,l,mid); //遞歸建左子樹 build(n*2+1,mid+1,r); //遞歸建左子樹 pushup(n); //求該點(diǎn)的權(quán)值 } void updateOne(int n,int idx,int C) { int l = t[n].l;//左端點(diǎn) int r = t[n].r;//右端點(diǎn) if(l==r) //l==r,到了葉子節(jié)點(diǎn) { t[n].val = C; //更新權(quán)值 return; } int mid = (l+r)>>1; if(idx<=mid) //val[idx]由左節(jié)點(diǎn)維護(hù) updateOne(n*2,idx,C); else //val[idx]由右節(jié)點(diǎn)維護(hù) updateOne(n*2+1,idx,C); pushup(n); //向上更新 } int query(int n,int L,int R) { int l = t[n].l; int r = t[n].r; if(L<=l && r<=R) //被包括直接返回 return t[n].val; int ans = 0; //保持答案 int mid = (l+r)>>1; if(L<=mid) //查詢區(qū)間與左節(jié)點(diǎn)維護(hù)區(qū)間有交集 ans = max(ans, query(2*n,L,R));//加上左節(jié)點(diǎn)交集區(qū)間的答案 if(R>mid) ans = max(ans, query(2*n+1,L,R));//加上右節(jié)點(diǎn)交集區(qū)間的答案 return ans; } int main() { int N, M;while(scanf("%d%d", &N, &M)!=EOF){for(int i = 1;i <= N;i ++)scanf("%d", &val[i]);build(1,1,N);while(M--){char C; int A, B;getchar();scanf("%c%d%d", &C, &A, &B);if(C=='Q')printf("%d\n", query(1,A,B));elseupdateOne(1,A,B);}}return 0; }
?
?
?
HDU1698
https://vjudge.net/problem/HDU-1698
Input
題目有多組輸入:
第一行輸入一個整數(shù)T,表示數(shù)據(jù)組數(shù)
對于每組輸入:
第一行整數(shù)N:繩子有多少段,而默認(rèn)每段繩子價值為1
第二行整數(shù)Q:表示更新操作的次數(shù)
接下來Q行每行輸入:X Y Z:表示將[X, Y]段變?yōu)閮r值Z
?
Output
輸出繩子的總價值
#include <iostream> using namespace std; const int N = 1e5+10; const int maxh = 4*N; struct node { int l; //左邊界 int r; // 右邊界 int val, lazy; //節(jié)點(diǎn)所維護(hù)的區(qū)間[l, r]的權(quán)值, 懶惰標(biāo)記 }t[maxh];//N為總節(jié)點(diǎn)數(shù) void pushup(int n) { t[n].val = t[2*n].val+t[2*n+1].val; //總區(qū)間和 = 左區(qū)間和+右區(qū)間和 } void build(int n,int l,int r) { t[n].l = l; //記錄維護(hù)的原數(shù)組的左端點(diǎn) t[n].r = r; //記錄維護(hù)的右端點(diǎn) t[n].lazy = 0; //標(biāo)記下懶惰數(shù)組 if(l==r){ //l==r,表示葉子節(jié)點(diǎn), t[n].val = 1; //因?yàn)閘==r,那么這個節(jié)點(diǎn)維護(hù)的值就是原數(shù)組val[l]的值 return; } int mid = (l+r)>>1; build(n*2,l,mid); //遞歸建左子樹 build(n*2+1,mid+1,r); //遞歸建左子樹 pushup(n); //求該點(diǎn)的權(quán)值 } void pushdown(int n) { int l = t[n].l; int r = t[n].r; if(l==r)//葉子節(jié)點(diǎn)沒有子結(jié)點(diǎn) return; if(t[n].lazy)//懶惰標(biāo)記不為0才能向下更新 { int mid = (l+r)/2; t[2*n].val = t[n].lazy*(mid-l+1);//更新左節(jié)點(diǎn)權(quán)值 t[2*n+1].val = t[n].lazy*(r-mid);//更新右節(jié)點(diǎn)權(quán)值 t[2*n].lazy = t[n].lazy; //更新左節(jié)點(diǎn)標(biāo)記 t[2*n+1].lazy = t[n].lazy; //更新右節(jié)點(diǎn)標(biāo)記 t[n].lazy = 0; //清除標(biāo)記 } } void updateRange(int n,int L,int R,int C) { int l = t[n].l; int r = t[n].r; if(L<=l && r<=R) //待更新區(qū)間為[L, R],而[l, r]是[L, R]的子集所以更新 { t[n].val = (r-l+1)*C; //更新權(quán)值 t[n].lazy = C; //更新標(biāo)記 return; } pushdown(n); //向下更新 int mid = (l+r)>>1; if(L<=mid) //左節(jié)點(diǎn)維護(hù)的區(qū)間與[L, R]有交集 updateRange(2*n,L,R,C); if(R>mid) //右節(jié)點(diǎn)維護(hù)的區(qū)間與[L, R]有交集 updateRange(2*n+1,L,R,C); pushup(n); //向上更新 } int main() { int T, cnt = 1;scanf("%d", &T);while(T--){int N, Q;scanf("%d", &N);build(1,1,N);scanf("%d", &Q);while(Q--){int X, Y, Z;scanf("%d%d%d", &X, &Y, &Z);updateRange(1,X,Y,Z);}printf("Case %d: The total value of the hook is %d.\n", cnt++, t[1].val); }return 0; }
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/YukiNote/p/11301577.html
總結(jié)
- 上一篇: 一套家具多少钱啊?
- 下一篇: Prometheus+Granfana