- 線段樹 -
線段樹:
線段樹(Segment Tree)
是一種基於分治思想
的二叉樹結構
,用於在區間
上進行信息統計
,區間可加減性
是它的一個重要性質。與按照二進制位(2的次冪)進行區間劃分的樹狀數組相比,線段樹是一種更加通用的結構,樹狀數組能做的線段樹都能做,不過樹狀數組會更快一些,需要的內存也比線段樹小。存儲線段樹的數組一般要開到題目中數據的四倍大小才能確保夠大:
1、線段樹的每個節點都代表一個區間
;
2、線段樹具有唯一的根節點,代表的區間是整個統計範圍,如 [1,N];
3、線段樹的每個葉節點都代表一個長度爲1的元區間 [x,x];
4、對於每個內部節點 [l,r],它的左子節點是 [l,mid],右子節點是 [mid+1,r],其中 mid=(l+r)/2 (向下取整)。
一、給定一個長度爲n的序列,有兩個操作,第一個在區間[l,r]上增加x,第二個詢問區間[l,r]的和:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[100005];
int d[100005<<2], b[100005<<2];//區間和,lazy標記
inline void pushup(int p) {//標記上傳
d[p]=d[p<<1]+d[(p<<1)|1];//d[p]=d[p*2]+d[p*2+1]
return ;
inline void pushdown(int s, int t, int p) {//標記下傳
int mid=(s+t)>>1;
d[p<<1]+=(mid-s+1)*b[p];
d[(p<<1)|1]+=(t-mid)*b[p];
b[p<<1]+=b[p];
b[(p<<1)|1]+=b[p];
b[p]=0;
}
inline void build(int s, int t, int p) {//建樹
if(s==t) {//若是葉子結點
d[p]=a[s];//區間和=它本身
return ;
}
int mid=(s+t)>>1;//mid=(s+t)/2區間中點
build(s, mid, p<<1);//左子節點
build(mid+1, t, (p<<1)|1);//右子節點
pushup(p);//更新父節點
}
inline void update(int l, int r, int c, int s, int t, int p) {
//把區間[l,r]加c,p是點號,s和t是這個點號代表的區間
if(1<=s && t<=r) {//若是l~r的子區間
d[p]+=(t-s+1)*c;
b[p]+=c;//標記
return ;
}
//若沒有完全覆蓋
if(b[p]) pushdown(s, t, p);//若有標記,則要即時下傳
int mid=(s+t)>>1;
if(l<=mid) update(l, r, c, s, mid, p<<1);//左子節點有部分要更新
if(r>mid) update(l, r, c, mid+1, t, (p<<1)|1);//右子節點有部分要更新
pushup(p);//更新父節點
}
inline int getans(int l, int r, int s, int t, int p) {
//查詢[l,r],p是節點,s和t是這個節點對應的區間
int ans=0;
if(l<=s && t<=r) {//完全包含
return d[p];
}
//否則訪問子節點
if(b[p]) pushdown(s, t, p);
int mid=(s+t)>>1;
if(l<=mid) ans+=getans(l, r, s, mid, p<<1);//如果在左邊
if(r>mid) ans+=getans(l, r, mid+1, t, (p<<1)|1);
return ans;
}
int main() {
int n, m;//n是區間長度,m是操作個數
scanf("%d %d", &n, &m);
for(register int i=1; i<=n; i++) scanf("%d", &a[i]);//輸入a[i]
build(1, n, 1);//開始建樹
for(register int i=1; i<=m; i++) {
int opt=0;//操作
scanf("%d", &opt);
if(opt==1) {//在區間[l,r]上增加c
int l, r, c;
scanf("%d %d %d", &l, &r, &c);
update(l, r, c, 1, n, 1);//更新區間, 增加值,區間,節點
}
if(opt==2) {//詢問區間[l,r]的和
int l, r;
scanf("%d %d", &l, &r);
printf("%d\n", getans(l, r, 1, n, 1));//求和並輸出結果
}
}
return 0;
}
二、給定一個長度爲n的序列,一開始全是0,有兩個操作,一個是將[l,r]全改成c,一個是詢問[l,r]不同的數字個數:(考慮邊界)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[100005<<2], num[100005<<2], b[100005<<2], pre[100005<<2], las[100005<<2];
//數組,區間不同的顏色(數字)個數,lazy標記,區間前面的數,區間後面的數
inline void pushup(int p) {//標記上傳
if(las[p<<1]==pre[(p<<1)|1]) num[p]=num[p<<1]+num[(p<<1)|1]-1;
//若左子節點後面=右子節點前面,則不同顏色數爲左右子節點之和減一
else num[p]=num[p<<1]+num[(p<<1)|1];//否則
pre[p]=pre[p<<1];
las[p]=las[(p<<1)|1];
}
inline void pushdown(int s, int t, int p) {//標記下傳
int mid=(s+t)>>1;
num[p<<1]=1;
num[(p<<1)|1]=1;
pre[p<<1]=pre[p];
las[p<<1]=las[p];
pre[(p<<1)|1]=pre[p];
las[(p<<1)|1]=las[p];
b[p<<1]=b[p];
b[(p<<1)|1]=b[p];
b[p]=0;
}
inline void build(int s, int t, int p) {//建樹
if(s==t) {//若是葉子節點
num[p]=1;
pre[p]=0;
las[p]=0;
return ;
}
int mid=(s+t)>>1;
build(s, mid, p<<1);
build(mid+1, t, (p<<1)|1);
pushup(p);
}
inline void update(int l, int r, int c, int s, int t, int p) {//更新區間[l,r]的信息
if(l<=s && t<=r) {
num[p]=1;
las[p]=pre[p]=c;
b[p]=c;
return ;
}
if(b[p]) pushdown(s, t, p);
int mid=(s+t)>>1;
if(l<=mid) update(l, r, c, s, mid, p<<1);
if(r>mid) update(l, r, c, mid+1, t, (p<<1)|1);
pushup(p);
}
inline int getans(int l, int r, int s, int t, int p) {
if(l<=s && t<=r) {
return num[p];
}
if(b[p]) pushdown(s, t, p);
int mid=(s+t)>>1;
int ans=0;
if(l<=mid) ans+=getans(l, r, s, mid, p<<1);
if(r>mid) ans+=getans(l, r, mid+1, t, (p<<1)|1);
if(las[p<<1]==pre[(p<<1)|1]) ans--;
return ans;
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
build(1, n, 1);
for(register int i=1; i<=m; i++) {
int opt, l, r;
scanf("%d", &opt);
if(opt==0) {
int c;
c=0;
scanf("%d %d %d", &l, &r, &c);
update(l, r, c, 1, n, 1);
}
if(opt==1) {
scanf("%d %d", &l, &r);
printf("%d\n", getans(l, r, 1, n, 1));//求區間不同數字的個數並輸出答案
}
}
return 0;
}
三、給定一個長度爲n的序列,詢問[l,r]的最大子段和:
*最大子段和:[l,r]的最大子段和爲存在一個區間[x,y]屬於[l,r]滿足對於任意屬於[l,r]滿足sum(i,j)<=sum(x,y)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
template<typename sp>
inline void qread(sp &x) {
char ch=getchar(), lst=' ';
while(ch<'0' || ch>'9') lst=ch, ch=getchar();
while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
if(lst=='-') x=-x;
}
template<typename sp>
sp mmax(sp a, sp b) {
return a>b ? a : b;
}
int a[100005];
struct Node{
int ans, pre, las, sum;//最大子段和,區間左邊部分的和,區間右邊部分的和,整個區間的和
}d[100005<<2];
inline void pushup(int p) {//上傳數據更新
d[p].ans=mmax(mmax(d[p<<1].ans, d[(p<<1)|1].ans), d[(p<<1)|1].pre+d[p<<1].las);
d[p].pre=mmax(d[p<<1].pre, d[p<<1].sum+d[(p<<1)|1].pre);
d[p].las=mmax(d[(p<<1)|1].las, d[(p<<1)|1].sum+d[p<<1].las);
d[p].sum=d[p<<1].sum+d[(p<<1)|1].sum;
}
inline void build(int s, int t, int p) {//建樹
if(s==t) {
d[p].ans=a[s];
d[p].pre=a[s];
d[p].las=a[s];
d[p].sum=a[s];
return ;
}
int mid=(s+t)>>1;
build(s, mid, p<<1);
build(mid+1, t, (p<<1)|1);
pushup(p);
}
inline Node getans(int l, int r, int s, int t, int p) {//區間查詢
if(l<=s && t<=r) return d[p];
int mid=(s+t)>>1;
if(r<=mid) return getans(l, r, s, mid, p<<1);
else if(l>mid) return getans(l, r, mid+1, t, (p<<1)|1);
else {
Node ans;
Node lans=getans(l, r, s, mid, p<<1), rans=getans(l, r, mid+1, t, (p<<1)|1);
ans.pre=mmax(lans.pre, lans.sum+rans.pre);
ans.las=mmax(rans.las, rans.sum+lans.las);
ans.ans=mmax(mmax(rans.ans, lans.ans), lans.las+rans.pre);
return ans;
}
}
int main() {
int n, m;
qread(n); qread(m);
for(register int i=1; i<=n; i++) qread(a[i]);
build(1, n, 1);
for(register int i=1; i<=n; i++) {
int l, r;
qread(l); qread(r);
printf("%d\n", getans(l, r, 1, n, 1).ans);
}
return 0;
}