區間最值與線段樹
區間最值問題:
有如下無序序列,求任意子區間段的最大值。
接著,我們要用分治的思想來快速地解決上面的問題。在解決問題之前,先介紹一些分治的概念。
二分查詢
二分查詢是分治思想的典型運用
我們有如下序列:
A1,A2,A3……An.
要查詢其中等於b的元素。一種方法就是一個個對比,看看是不是相等,時間複雜度為N。還有就是二分查詢。
如上圖,通過不斷地與中間元素對比,縮小搜尋區間,最後得到A4==b.時間複雜度為logN.
二叉搜尋樹
在二分查詢中,我們要求佇列是有序地。那麼如果佇列無序又要如何?我們可以仿造二分查詢的方式,構造一個二叉搜尋樹,如下:
二叉搜尋樹左節點比根小,右節點比根大。上圖紅色箭頭代表搜尋A4的過程。
線段樹
線段樹也是一種二叉搜尋樹,我們回到區間最值的問題,有如下無序序列
現在我們要在上面的序列中,搜尋任意指定區間的最大值。
我們先來解決總的最大值的問題
一個基本的方法就是一個個比較,取出最大值,時間複雜度為N。
我們借用二分搜尋的分治思想。可以把序列劃分為1-5節點的最大值和6-8的最大值,再取這兩個值的較大值。遞迴地,我們再把1-5繼續劃分至只剩一個元素。這樣的時間複雜度為logN .如下面的過程,即線段數搜尋的過程:
上面的過程就是選取區間最大值的方式,總的最大值為9.
接著,我們要處理任意指定子序列的最值問題。
根據上圖我們可以看出,根據中間的結果,可以和容易地尋找1-6個節點的最大值,分治為1-4的最大值7,5-6的最大值8,1-6的最大值就為8.
總結一下上述的思想。
首先,有點像總統選舉,我們要尋找一個國家最適合當總統的人,不需要一個個去比較,只需要每個鄉選一個最好的,再在縣裡比較,得出一個縣裡最好的,然後市,然後省,最後我們得到了全國最好的。
然後,我們選舉的過程中,得到了鄉,縣的中間結果,這些又可以用來選取任意小範圍的最好人選。如我們要選總管廣東,廣西和南京鼓樓區第二大街的總管,只需要用到不同級別的中間結果彙總即可。
線段樹最終回到分治的思想上來,其應用與如下領域:
區間最值查詢問題
連續區間修改或者單節點更新的動態查詢問題
多維空間的動態查詢
線段樹的程式設計實踐:
線段樹的節點結構為:
struct node { int left; int right; int max; };
其中max儲存當前線段的最大值。
線段樹最基本要有三個函式:
1.遞迴地建立樹:
void buildtree(int index,int left,int right)
{
tree[index].left=left;
tree[index].right=right;
tree[index].max=0;
if (left==right)
{
return;
}
int mid=(left+right)>>1;
buildtree((index<<1)+1,left,mid);
buildtree((index<<1)+2,mid+1,right);
return;
}
2.遞迴地插入:
void insert(int index,int left,int right,int k)
{
int mid=(tree[index].left+tree[index].right)>>1;
if (tree[index].left==tree[index].right)
{
tree[index].max=k;
return ;
}
if (right<=mid)
{
if(tree[index].max<=k)
tree[index].max=k;
insert((index<<1)+1,left,right,k);
return;
}
else
if (left>mid)
{
if(tree[index].max<=k)
tree[index].max=k;
insert((index<<1)+2,left,right,k);
return;
}
}
3.遞迴地查詢:
int query(int index,int left,int right)
{
int mid=(tree[index].left+tree[index].right)>>1;
if (tree[index].left==tree[index].right)
{
return tree[index].max;
}
if (right<=mid)
{
return query((index<<1)+1,left,right);
}
else
if (left>mid)
{
return query((index<<1)+2,left,right);
}
else
{
return M(query((index<<1)+1,left,mid),query((index<<1)+2,mid+1,right));
}
}
最後,求解任意區間最值的程式碼如下:
#include<iostream>
using namespace std;
#define maxn 1001
int N,Q;
int M(int a,int b)
{
return a>=b?a:b;
}
struct node
{
int left;
int right;
int max;
};
struct node tree[maxn*4];
void buildtree(int index,int left,int right)
{
tree[index].left=left;
tree[index].right=right;
tree[index].max=0;
if (left==right)
{
return;
}
int mid=(left+right)>>1;
buildtree((index<<1)+1,left,mid);
buildtree((index<<1)+2,mid+1,right);
return;
}
void insert(int index,int left,int right,int k)
{
int mid=(tree[index].left+tree[index].right)>>1;
if (tree[index].left==tree[index].right)
{
tree[index].max=k;
return ;
}
if (right<=mid)
{
if(tree[index].max<=k)
tree[index].max=k;
insert((index<<1)+1,left,right,k);
return;
}
else
if (left>mid)
{
if(tree[index].max<=k)
tree[index].max=k;
insert((index<<1)+2,left,right,k);
return;
}
}
int query(int index,int left,int right)
{
int mid=(tree[index].left+tree[index].right)>>1;
if (tree[index].left==tree[index].right)
{
return tree[index].max;
}
if (right<=mid)
{
return query((index<<1)+1,left,right);
}
else
if (left>mid)
{
return query((index<<1)+2,left,right);
}
else
{
return M(query((index<<1)+1,left,mid),query((index<<1)+2,mid+1,right));
}
}
int main()
{
int left,right,a;
cin>>N;
buildtree(0,1,N);
for(int i=1;i<=N;i++)
{
cin>>a;
insert(0,i,i,a);
}
cin>>Q;
while(Q--)
{
cin>>left>>right;
cout<<query(0,left,right)<<endl;
}
return 0;
}