線段樹原理詳解(單點更新 區間查詢)
這道題是線段樹的 單點更新和區間查詢最大值
(AC程式碼在最後)
通過這道題 理解下線段樹
線段樹的原理
關於原理 網上有很多講解 大家可以自己學習 這裡不單獨贅述原理
而是結合程式碼講解下 原理也不難 相信大家看了程式碼仔細看下邊的解釋可以理解
另外 推薦兩篇聚聚寫的部落格 寫的很詳細~~~~(但是講的很深入很系統 自行選擇重點 反正我看的頭暈)
然後下面主要講下程式碼 都是個人理解 不足請指出
基本結構
struct NODE{ int value,left,right; }node[maxnode]; int father[maxn];
node是線段樹的結構 每個節點代表一個區間 left 和right代表區間範圍 value在這道題中代表區間的最大值
father陣列用來記錄葉子節點線上段樹中的位置 這些不太懂不要緊 下邊會解釋
建樹
建樹採用的是順序儲存 儲存關係是 左孩子=父親x2 右孩子等於父親x2+1
舉個例子 給你區間 1-5的陣列 分別是 1 2 3 4 5
則建立的線段樹 如下圖所示 紅色的是每個點在這棵樹中的節點編號 比如1號代表的就是區間【1,5】
[1,5]1 | |||||||||
[1,3] 2 [4,5] 3 | |||||||||
4 [1,2] [3,3] 5 [4,4] 6 [5,5]7 | |||||||||
8【1,1】 9【2,2】 10 11 12 13 14 15 |
而葉子節點代表的就是數字本身 如12號 區間【3,3】 就是原始資料中的3號
在本題中 每個節點代表的是該區間的最大值(有的題也可以代表區間和)
所以 各個葉子節點的值就是 所代表的節點的值
而father陣列則是用來記錄原始資料中各個點線上段樹中對應的葉子節點的編號 如 father【3】=5;
建樹程式碼及註釋
void BuildTree(int i,int left,int right){
node[i].left=left;//i是各個節點線上段樹中的位置編號
node[i].right=right;
node[i].value=0;
if(left==right){//到了葉子節點
father[left]=i;return ;//left就是此葉子節點所代表的原資料的編號
}
BuildTree(i<<1,left,(left+right)/2);
BuildTree((i<<1)+1,((left+right)/2)+1,right);
}
單點更新
單點更新是自下而上的
比如說 我們要把 1 2 3 4 5 中的3 更新為6
那麼 首先 我們通過剛才的father陣列可以知道 3 存線上段樹中的5號位置 並且葉子節點代表的區間就是一個點
所以我們直接 使 node[ father[ 3 ] ].value=6 就修改了3號的值
然後 我們通過 5/2 得到了 他的父親節點2 而通過2*2 和2*2+1 得到了4 和5 更新後的兩個孩子節點
取兩個中的最大值 則完成了2號節點的更新 以此類推 向上更新 直到根節點
程式碼
void UpdateTree(int ri){
if(ri==1)return ;
int fi=ri/2;
int a=node[fi<<1].value;
int b=node[(fi<<1)+1].value;
node[fi].value=max(a,b);
UpdateTree(ri/2);//向上遞迴更新
}
區間查詢最大值
區間查詢與單點更新相反 區間查詢是自上而下的
比如 如果要查詢【1,5】區間的最大值 那就直接是 node【1】的值
而如果要查詢區間【3,4】的最大值的話 就需要自上而下查詢
首先【1,5】分為左孩子【1,3】和右孩子【4,5】 發現所查詢區間在兩個區間中都有涉及
那麼就查詢左孩子中【3,3】右孩子中【4,4】 取其最大值
所以 整個流程就是 判斷所查區間否完全在節點區間範圍內 若在 則往下查 不在 則只查所需要的區間
具體如何判斷 看程式碼
void Query(int i,int l,int r){
if(node[i].left==l&&node[i].right==r){//與所查區間完全重合
Max=max(Max,node[i].value);return ;
}
i=i<<1;//判斷左孩子
if(l<=node[i].right){//涉及左孩子
if(r<=node[i].right)Query(i,l,r);//完全在左孩子裡
else Query(i,l,node[i].right);//不完全在
}
i++;//右孩子
if(r>=node[i].left){//涉及右孩子
if(l>=node[i].left) Query(i,l,r);//完全在右孩子裡
else Query(i,node[i].left,r);//不完全右孩子
}
}
以上是線段樹單點更新和區間最大值查詢的原理及理解
下面是hdu1754題解程式碼
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=2e6+7;
const int maxnode=1<<19;
struct NODE{
int value,left,right;
}node[maxnode];
int father[maxn];
int n,m,g,x,y;
void BuildTree(int i,int left,int right){
node[i].left=left;//i是各個節點線上段樹中的位置編號
node[i].right=right;
node[i].value=0;
if(left==right){//到了葉子節點
father[left]=i;return ;//left就是此葉子節點所代表的原資料的編號
}
BuildTree(i<<1,left,(left+right)/2);
BuildTree((i<<1)+1,((left+right)/2)+1,right);
}
void UpdateTree(int ri){
if(ri==1)return ;
int fi=ri/2;
int a=node[fi<<1].value;
int b=node[(fi<<1)+1].value;
node[fi].value=max(a,b);
UpdateTree(ri/2);//向上遞迴更新
}
int Max;
void Query(int i,int l,int r){
if(node[i].left==l&&node[i].right==r){//與所查區間完全重合
Max=max(Max,node[i].value);return ;
}
i=i<<1;//判斷左孩子
if(l<=node[i].right){//涉及左孩子
if(r<=node[i].right)Query(i,l,r);//完全在左孩子裡
else Query(i,l,node[i].right);//不完全在
}
i++;//右孩子
if(r>=node[i].left){//涉及右孩子
if(l>=node[i].left) Query(i,l,r);//完全在右孩子裡
else Query(i,node[i].left,r);//不完全右孩子
}
}
int main(){
ios::sync_with_stdio(false);
while(cin>>n>>m){
BuildTree(1,1,n);
for(int i=1;i<=n;i++){
cin>>g;
node[father[i]].value=g;
UpdateTree(father[i]);
}
while(m--){
string op;
cin>>op>>x>>y;
if(op[0]=='Q'){
Max=0;
Query(1,x,y);
cout<<Max<<endl;
}
else{
node[father[x]].value=y;
UpdateTree(father[x]);
}
}
}
return 0;
}