P3372 模板 線段樹 1

2022-03-04 20:57:54 字數 4467 閱讀 9612

馬上就又要考試了,複習一下板子,以前學線段樹的時候還很懵,現在回過頭來補一下檔。。。

如題,已知乙個數列,你需要進行下面兩種操作:

1.將某區間每乙個數加上x

2.求出某區間每乙個數的和

輸入格式:

第一行包含兩個整數n、m,分別表示該數列數字的個數和操作的總個數。

第二行包含n個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。

接下來m行每行包含3或4個整數,表示乙個操作,具體如下:

操作1: 格式:1 x y k 含義:將區間[x,y]內每個數加上k

操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和

輸出格式:

輸出包含若干行整數,即為所有操作2的結果。

輸入樣例#1:

5 5

1 5 4 2 3

2 2 4

1 2 3 2

2 3 4

1 1 5 1

2 1 4

輸出樣例#1:

11

820

時空限制:1000ms,128m

資料規模:

對於30%的資料:n<=8,m<=10

對於70%的資料:n<=1000,m<=10000

對於100%的資料:n<=100000,m<=100000

(資料已經過加強^_^,保證在int64/long long資料範圍內)

樣例說明:

本題是最基礎的線段樹模板——區間加減、區間求和。

我這裡提供三種方法:1、線段樹  2、樹狀陣列  3、分塊

簡單講一下線段樹(鑑於線段樹網上的講解已經十分清楚詳細,我就隨便將點理解):

線段樹是一種基於分治思想的二叉樹結構,用於區間上進行資訊統計。

線段樹的每個節點維護的是乙個區間; 線段樹具有唯一的根節點表示整個統計範圍($[1,n]$); 線段樹的每個葉子節點都表示乙個長度為1的單元區間($[x,x]$); 對於每個內部節點$[l,r]$,它的左子節點為$[l,mid]$,右子節點為$[mid+1,r]$,其中$mid=(l+r)/2$。

建樹:

根節點編號為$1$,編號為x的左子節點為$x*2$,右兒子節點為$x*2+1$。依此,遞迴建樹,同時在遞迴過程中預處理所需資訊(以本題為例,在遞迴建樹時我們可以維護出每個子區間的和)。容易發現,當有n個葉子節點時,該樹總共的節點數最多為$n+n/2+n/4+…+2+1=2n-1$(即一顆滿二叉樹)。於是儲存線段樹的陣列長度至少為$4n$,才能保證不會越界。

區間修改:

從上往下遞迴,判斷區間是否包含,若當前的區間完全被$[l,r]$覆蓋則直接更改,若不覆蓋則繼續遞迴左右兒子節點,這裡我的**實現時用到了延遲標記(add[rt]表示編號rt的區間所增加的值),顧名思義就是先不將修改的值下放到子區間中而是在當前區間要被修改或被查詢時再進行修改(詳見**)。最後記得回溯時維護所需資訊(本題中的區間和)。

區間查詢:

與區間修改類似,判斷區間是否包含,若被完全覆蓋直接回溯並返回當前區間的資訊(本題中的區間和),否則就繼續遞迴左右兒子節點,當然也得記得要在找到完全覆蓋區間之前下放延遲標記。最後返回所需的區間資訊就ok了(本題中的區間和)。

線段樹**:

#include#define il inline

#define ll long long

#define debug printf("%d %s\n",__line__,__function__)

#define lson l,m,rt<<1

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

using

namespace

std;

const

int n=100005

;ll n,m,sum[n

<<2],add[n<<2

];il ll gi()

il void pushup(int rt)

il void pushdown(int rt,intm)}

il void build(int l,int r,int

rt)

int m=l+r>>1

; build(lson),build(rson);

pushup(rt);

}il

void update(int l,int r,ll c,int l,int r,int

rt) pushdown(rt,r-l+1

);

int m=l+r>>1

;

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

if(m

pushup(rt);

}il ll query(

int l,int r,int l,int r,int

rt)int

main()

else

}return0;

}

樹狀陣列思路:再談談樹狀陣列,由於這道題只是區間修改和區間查詢,於是我們可以用**量超少且常數更小的樹狀陣列去做啦,大致就是用差分約束存數並修改,再用字首和+輔助陣列來求和,我記得以前寫過樹狀陣列的詳解,可以去看一下,所以這裡就不解釋了。(其實是我有點忘了~~!)

樹狀陣列**:

#include#define maxn 200005

using

namespace

std;

#define ll long longll a[maxn],b[maxn],c[maxn],n,m;

inline ll getint()

return f?-a:a;

}void update(ll *x,ll k,ll num)

}ll read(ll *x,ll k)

return

sum;

}int

main()

while(m--)

else

}return0;

}

分塊思路:然後扯下炒雞優美的暴力——分塊。思路比較簡單,就是將整個區間分為$\sqrt n$塊,統計出每塊內的$sum$,然後對於修改則用思想「大段維護、區域性樸素」,也就是對於區間中間整體包含的直接累加(更改則打標記,用$add$陣列記錄某塊的增量),兩邊多餘的則暴力累加(更改也是暴力,注意這裡更改的是原數,並不影響開始每塊的$sum$值),具體實現看**體會(話說分塊比線段樹還跑的快,可見洛谷資料也是有點水~~)。

分塊**:

#include#define il inline

#define ll long long

#define debug printf("%d %s\n",__line__,__function__)

using

namespace

std;

const

int n=100005

;ll a[n],sum[n],add[n];

intpos[n],n,m;

struct

datat[n];

il ll gi()

il void change(int l,int

r,ll d)

else

}il ll ask(

int l,int

r)

for(int i=p+1;i<=q-1;i++)ans+=sum[i]+add[i]*(t[i].r-t[i].l+1

);

for(int i=l;i<=t[p].r;i++)ans+=a[i];

ans+=add[p]*(t[p].r-l+1

);

for(int i=t[q].l;i<=r;i++)ans+=a[i];

ans+=add[q]*(r-t[q].l+1

);

return

ans;

}int

main()

if(t[s].r1].r+1,t[s].r=n;

for(int i=1;i<=s;i++)

for(int j=t[i].l;j<=t[i].r;j++)pos[j]=i,sum[i]+=a[j];

intu,v,w,z;

while(m--)

else

}return0;

}

P3372 模板 線段樹 1

線段樹學習 這個題來看,線段樹分為建樹,更新,查詢。1.建樹 void build ll p,ll l,ll r ll mid l r 1 build lson p l,mid build rson p mid 1,r push up sum p void push up sum ll p 這段 的...

P3372 模板 線段樹 1

題 include includeusing namespace std typedef long long ll ll n,m,ans,x,y,op,val 因為下面有的函式需要用到x,y,val值,懶得傳參,故直接寫為全域性變數 const int n 100000 struct nodetre...

P3372 模板 線段樹 1

題目描述 如題,已知乙個數列,你需要進行下面兩種操作 1.將某區間每乙個數加上x 2.求出某區間每乙個數的和 輸入輸出格式 輸入格式 第一行包含兩個整數n m,分別表示該數列數字的個數和操作的總個數。第二行包含n個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。接下來m行每行包含3或4個整數...