線段樹(區間和,最大值,最小值,區間動態更新查詢)
阿新 • • 發佈:2019-02-02
//===========================================
//segment tree
//final version
//by kevin_samuel(fenice)
//本模板為轉載模板,後面的註釋和主函式的驗證為Alei新增
#include <iostream>
#include <cstdio>
#include <cmath>
//線段樹
using namespace std;
#define MAXN 100
#define INF 0x3fffffff
int A[MAXN]; //操作的序列,記得為(1...n)非(0...n)
//int max;
//int min;
struct node
{
int left;
int right;
int max; //維護最大值
int sum; //維護區間和
int min; //維護最小值
}Tree[MAXN<<2]; //儲存線段樹
void maintain(int root) //向上調整,使得讓線段樹維護區間最小值最大值區間和
{
int LC = root<<1; //此根的左孩子
int RC = (root<<1)+1; //此根的右孩子
Tree[root].sum = Tree[LC].sum + Tree[RC].sum; //根的區間和
Tree[root].max = max(Tree[LC].max,Tree[RC].max); //根的最大值
Tree[root].min = min(Tree[LC].min,Tree[RC].min); //根的最小值
}
void Build(int root,int start,int end) //構建線段樹
{ //初始化時傳入Build(1,1,n);
Tree[root].left = start; //建區間大小
Tree[root].right = end;
if(start == end) //當到達葉子節點時
{
Tree[root].sum = A[start];
Tree[root].max = A[start];
Tree[root].min = A[start];
return;
}
int mid = (start + end)>>1; //中間分開
Build(root<<1,start,mid); //對左孩子建樹,左邊孩子的編號為root*2
Build((root<<1)+1,mid+1,end); //對右邊孩子建樹
maintain(root);
}
void update(int root,int pos,int value) //更新點的值
{
if(Tree[root].left == Tree[root].right && Tree[root].left == pos) //更新葉子節點的值
{
Tree[root].sum += value;
Tree[root].max += value;
Tree[root].min += value;
return;
}
int mid = (Tree[root].left + Tree[root].right)>>1; //中間分開成兩個區間
if(pos <= mid) //更新的值在左孩子
update(root<<1,pos,value); //更新左孩子
else
update((root<<1)+1,pos,value); //更新的值在右孩子
maintain(root); //葉子節點更新完成後,會回溯到他的父節點,這樣一直往上更新到根節點,維護線段樹性質
}
int Query(int root,int start,int end) //查詢區間和(start, end)根節點為1
{
if(start == Tree[root].left && Tree[root].right == end) //正好匹配到查詢區間,直接返回區間和
{
return Tree[root].sum;
}
int mid = (Tree[root].left + Tree[root].right)>>1; //分開區間
int ret = 0;
if(end <= mid) //查詢結果在左邊區間
ret += Query(root<<1,start,end); //將左區間的查詢結果返回,並記錄在結果和中
else if(start >= mid+1) //查詢結果在右區間
ret += Query((root<<1)+1,start,end);
else //查詢結果包含在左右兩個區間中
{
ret += Query(root<<1,start,mid); //查左的一部分
ret += Query((root<<1)+1,mid+1,end); //查右的一部分
}
return ret; //返回本次查詢結果
}
int RminQ(int root,int start,int end) //查詢區間最小值
{
if(start == Tree[root].left && Tree[root].right == end) //正好匹配區間
{
return Tree[root].min;
}
int mid = (Tree[root].left + Tree[root].right)>>1; //區間分開,去查左右孩子
int ret = INF; //先把結果記錄為很大
if(end <= mid) // 完全左區間匹配
ret = min(ret,RminQ(root<<1,start,end));
else if(start >= mid+1) //完全右區間匹配
ret = min(ret,RminQ((root<<1)+1,start,end));
else
{
int a = RminQ(root<<1,start,mid);
int b = RminQ((root<<1)+1,mid+1,end);
ret = min(a,b); //求左右區間和匹配區間相符的最小值的較小值
}
return ret; //記得要返回本次查詢的結果
}
int RmaxQ(int root,int start,int end) //查詢區間最大值
{
if(start == Tree[root].left && Tree[root].right == end)
{
return Tree[root].max;
}
int mid = (Tree[root].left + Tree[root].right)>>1;
int ret = 0; //************可能是 (-INF)要儘可能的小
if(end <= mid)
ret = max(ret,RmaxQ(root<<1,start,end)); //完全左孩子區間匹配
else if(start >= mid+1)
ret = max(ret,RmaxQ((root<<1)+1,start,end)); //完全右孩子區間匹配
else
{
int a = RmaxQ(root<<1,start,mid);
int b = RmaxQ((root<<1)+1,mid+1,end);
ret = max(a,b); //求的左右兩個區間和匹配區間相符的最大值得較大者
}
return ret; //記得返回結果
}
int main()
{
for(int i = 1; i <= 10; i++)
A[i] = i;
Build(1,1,10);
cout << " (1..10)的陣列,對應值如下:" << endl;
for(int i = 1; i <= 10; i++)
cout << A[i] << " ";
cout << endl;
cout << "(1..5)區間最小值:" << RminQ(1,1,5) << endl;
cout << " (1..5)區間最大值:" << RmaxQ(1,1,5) << endl;
cout << " (1..5)區間和:" << Query(1,1,5) << endl;
cout << " 把位置1的值加上100: " << endl;
update(1, 1, 100);
cout << " (1..5)區間最小值" << RminQ(1,1,5) << endl;
cout << " (1..5)區間最大值:" << RmaxQ(1,1,5) << endl;
cout << " (1..5)區間和:" << Query(1,1,5) << endl;
return 0;
}
/*
*總結一下線段樹:
*(1)維護區間和,使得區間求和在O(log(n))的複雜度下完成
*(2)維護區間最小值,使得查詢區間最小值在O(log(n))的複雜度下完成
*(3)維護區間最大值
****************************************************************
*(4)更新序列中某個值,也能使得樹維護區間的最大,最小,和區間和。
*(5)基於區間和的應用,最小值得應用,例如序列的動態更新查詢
*/
//segment tree
//final version
//by kevin_samuel(fenice)
//本模板為轉載模板,後面的註釋和主函式的驗證為Alei新增
#include <iostream>
#include <cstdio>
#include <cmath>
//線段樹
using namespace std;
#define MAXN 100
#define INF 0x3fffffff
int A[MAXN]; //操作的序列,記得為(1...n)非(0...n)
//int max;
//int min;
struct node
{
int left;
int right;
int max; //維護最大值
int sum; //維護區間和
int min; //維護最小值
}Tree[MAXN<<2]; //儲存線段樹
void maintain(int root) //向上調整,使得讓線段樹維護區間最小值最大值區間和
{
int LC = root<<1; //此根的左孩子
int RC = (root<<1)+1; //此根的右孩子
Tree[root].sum = Tree[LC].sum + Tree[RC].sum; //根的區間和
Tree[root].max = max(Tree[LC].max,Tree[RC].max); //根的最大值
Tree[root].min = min(Tree[LC].min,Tree[RC].min); //根的最小值
}
void Build(int root,int start,int end) //構建線段樹
{ //初始化時傳入Build(1,1,n);
Tree[root].left = start; //建區間大小
Tree[root].right = end;
if(start == end) //當到達葉子節點時
{
Tree[root].sum = A[start];
Tree[root].max = A[start];
Tree[root].min = A[start];
return;
}
int mid = (start + end)>>1; //中間分開
Build(root<<1,start,mid); //對左孩子建樹,左邊孩子的編號為root*2
Build((root<<1)+1,mid+1,end); //對右邊孩子建樹
maintain(root);
}
void update(int root,int pos,int value) //更新點的值
{
if(Tree[root].left == Tree[root].right && Tree[root].left == pos) //更新葉子節點的值
{
Tree[root].sum += value;
Tree[root].max += value;
Tree[root].min += value;
return;
}
int mid = (Tree[root].left + Tree[root].right)>>1; //中間分開成兩個區間
if(pos <= mid) //更新的值在左孩子
update(root<<1,pos,value); //更新左孩子
else
update((root<<1)+1,pos,value); //更新的值在右孩子
maintain(root); //葉子節點更新完成後,會回溯到他的父節點,這樣一直往上更新到根節點,維護線段樹性質
}
int Query(int root,int start,int end) //查詢區間和(start, end)根節點為1
{
if(start == Tree[root].left && Tree[root].right == end) //正好匹配到查詢區間,直接返回區間和
{
return Tree[root].sum;
}
int mid = (Tree[root].left + Tree[root].right)>>1; //分開區間
int ret = 0;
if(end <= mid) //查詢結果在左邊區間
ret += Query(root<<1,start,end); //將左區間的查詢結果返回,並記錄在結果和中
else if(start >= mid+1) //查詢結果在右區間
ret += Query((root<<1)+1,start,end);
else //查詢結果包含在左右兩個區間中
{
ret += Query(root<<1,start,mid); //查左的一部分
ret += Query((root<<1)+1,mid+1,end); //查右的一部分
}
return ret; //返回本次查詢結果
}
int RminQ(int root,int start,int end) //查詢區間最小值
{
if(start == Tree[root].left && Tree[root].right == end) //正好匹配區間
{
return Tree[root].min;
}
int mid = (Tree[root].left + Tree[root].right)>>1; //區間分開,去查左右孩子
int ret = INF; //先把結果記錄為很大
if(end <= mid) // 完全左區間匹配
ret = min(ret,RminQ(root<<1,start,end));
else if(start >= mid+1) //完全右區間匹配
ret = min(ret,RminQ((root<<1)+1,start,end));
else
{
int a = RminQ(root<<1,start,mid);
int b = RminQ((root<<1)+1,mid+1,end);
ret = min(a,b); //求左右區間和匹配區間相符的最小值的較小值
}
return ret; //記得要返回本次查詢的結果
}
int RmaxQ(int root,int start,int end) //查詢區間最大值
{
if(start == Tree[root].left && Tree[root].right == end)
{
return Tree[root].max;
}
int mid = (Tree[root].left + Tree[root].right)>>1;
int ret = 0; //************可能是 (-INF)要儘可能的小
if(end <= mid)
ret = max(ret,RmaxQ(root<<1,start,end)); //完全左孩子區間匹配
else if(start >= mid+1)
ret = max(ret,RmaxQ((root<<1)+1,start,end)); //完全右孩子區間匹配
else
{
int a = RmaxQ(root<<1,start,mid);
int b = RmaxQ((root<<1)+1,mid+1,end);
ret = max(a,b); //求的左右兩個區間和匹配區間相符的最大值得較大者
}
return ret; //記得返回結果
}
int main()
{
for(int i = 1; i <= 10; i++)
A[i] = i;
Build(1,1,10);
cout << " (1..10)的陣列,對應值如下:" << endl;
for(int i = 1; i <= 10; i++)
cout << A[i] << " ";
cout << endl;
cout << "(1..5)區間最小值:" << RminQ(1,1,5) << endl;
cout << " (1..5)區間最大值:" << RmaxQ(1,1,5) << endl;
cout << " (1..5)區間和:" << Query(1,1,5) << endl;
cout << " 把位置1的值加上100: " << endl;
update(1, 1, 100);
cout << " (1..5)區間最小值" << RminQ(1,1,5) << endl;
cout << " (1..5)區間最大值:" << RmaxQ(1,1,5) << endl;
cout << " (1..5)區間和:" << Query(1,1,5) << endl;
return 0;
}
/*
*總結一下線段樹:
*(1)維護區間和,使得區間求和在O(log(n))的複雜度下完成
*(2)維護區間最小值,使得查詢區間最小值在O(log(n))的複雜度下完成
*(3)維護區間最大值
****************************************************************
*(4)更新序列中某個值,也能使得樹維護區間的最大,最小,和區間和。
*(5)基於區間和的應用,最小值得應用,例如序列的動態更新查詢
*/