如何關閉 IDEA 自動更新
阿新 • • 發佈:2020-12-10
線段樹——基礎
1.引語
線段樹,可謂是好用的資料結構(廢話)
2.線段樹作用
1.區間加和
2.區間查詢
3.區間最大值,最小值。。。
4.忘了
3.線段樹概念:
儲存:
線段樹需要用結構體儲存
程式碼:
struct tree{
int l,r;
long long pre,add,num;
}t[4*maxn];
其中,sum代表該節點維護的值,l,r代表該節點維護的區間範圍,add為懶標記
建樹
一個節點node,它的左兒子的編號為2*node,右節點為2*node+1。 其中,樹的每個沒有子節點的節點就是放置陣列a的 而每個根節點都是放置其兩個子節點的某些特性 (如加和,最大值,最小值....) 樹狀陣列的建樹影象可以看成是這樣:
當左區間和右區間所劃定的範圍相同,
也就是其為子節點,
那麼就可以將a陣列中對應的數值賦值到pre中。下面是程式碼:
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;//設立p點所對應的區間範圍為l-r
if(l==r)
{
t[p].pre=a[l];
return;
}
int mid=l+r>>1;//取中點
build(p*2,l,mid);//左子樹,編號為p*2,範圍為 l-mid
build(p*2+1,mid+1,r);//右子樹,編號為p*2+1,範圍為mid+1—r
t[p].pre+=t[p*2].pre+t[p*2+1].pre;//p點的值為左右兩個子書節點的值的和
}
區間查詢:
查詢的思想是這樣:假如說要求取1-3的加和
根據上面的圖來看,首先從根節點開始找,分成1-2,3-4
而此時我們發現,1-2正好包括在1-3中,那麼直接返回值,
接著,由於3-4不在範圍內,繼續往下找
分為3,4,3在範圍內,直接返回三。
運用到程式碼中長這樣:
long long ask(int p,int l,int r)
{
if(l<=t[p].l&&r>=t[p].r)//在範圍內
return t[p].pre;//直接返回值
else if(tree[i].r<l||tree[i].l>r)
return 0;//這個區間和目標沒有關係,即在目標之外,直接返回0
int mid=t[p].l+t[p].r>>1;
long long ans=0;
if(l<=mid)
ans+=ask(p*2,l,r);//如果這個區間的左兒子與目標區間有交集,那麼搜尋。
if(r>mid)
ans+=ask(p*2+1,l,r);//如果這個區間的右兒子與目標區間有交集,那麼搜尋。
return ans;
}
單點修改
演算法的目標是在dis的位置上加上一個k
知道目標還不是有手就行嗎,哦,我沒手啊
所以從根節點開始,看這個dis是左兒子還是右兒子。
比較好理解,因此直接上程式碼:
int add(int p,int k,int dis)
{
if(tree[p].l==tree[p].r)//如果這個區間被完全包括在目標區間裡面,那麼就直接新增賦值
{
tree[p].pre+=k;
return;
}
if(dis<=tree[i*2].r)
add(i*2,k,dis);//dis的區間判斷
else
add(i*2+1,k,dis);
tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//回溯時需要重新賦值
}
區間修改
思路:如果把這個區間加上k,相當於在這個區間上塗上一個k的標記,單點查詢時就把標記加上即可
int add(int p,int l,int r,int k)
{
if(tree[p].l>=l&&tree[p].r<=r)
{//如果這個區間被完全包括在目標區間裡面,講這個區間標記k
tree[p].num+=k;
return 0;
}
if(tree[i*2].r>=l)//左子樹的右區間的邊界小於左邊界
add(i*2,l,r,k);
if(tree[i*2+1].l<=r)
add(i*2+1,l,r,k);//右子樹的左邊區間小於右邊界
}
單點查詢
dis去哪,把路徑上所有標價加起來就好
void search(int p,int dis)
{
ans+=tree[p].num;//一路加起來
if(tree[p].l==tree[p].r)
return;
if(dis<=tree[p*2].r)
search(p*2,dis);
if(dis>=tree[p*2+1].l)
search(p*2+1,dis);
}