定义:
线段树是建立在线段的基础上,每个结点都代表了一条线段[a , b]。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为[a , (a + b ) / 2],右结点代表的线段为[( (a + b ) / 2)+1 , b]。
基本操作:
建树,插入,删除,查找
线段树的特征:
1、线段树的深度不超过logL(L是最长区间的长度)。
2、线段树把区间上的任意一条线段都分成不超过2logL条线段。
这些结论为线段树能在O(logL)的时间内完成一条线段的插入、删除、查找等工作,提供了理论依据
优化:
区间的端点不是整数,或者区间太大导致建树内存开销过大MLE ,那么就需要进行“离散化”后再建树。
例题:
给定Q(1 ≤ Q≤ 200,000)个数A1,A2… AQ,,多次求任一区间Ai–Aj中最大数和最小数的差。
#include<iostream>
using namespace std;
#define MY_MIN 1 << 31 - 1
#define MY_MAX 1- 1 << 31
struct CNode
{
int L,R;
int nMin,nMax;
CNode * pLeft,* pRight;
};
int nMin,nMax;
CNode Tree[1000001];
int nCount = 0;
void BuildTree(CNode * pRoot,int L,int R)
{
pRoot->L = L;pRoot->R = R;
pRoot->nMin = MY_MIN;pRoot->nMax = MY_MAX;
if(L!=R)
{
nCount ++;
pRoot->pLeft =Tree + nCount;
nCount ++;
pRoot->pRight =Tree + nCount;
BuildTree(pRoot->pLeft,L,(L+R) / 2);
BuildTree(pRoot->pRight,(L+R) / 2 + 1,R);
}
}
void Insert(CNode * pRoot,int i,int v)
{
if(pRoot->L == i && pRoot->R == i)
{
pRoot->nMin = pRoot->nMax = v;
return;
}
pRoot->nMin =_cpp_min(pRoot->nMin,v);
pRoot->nMax =_cpp_max(pRoot->nMax,v);
if(i <= (pRoot->L + pRoot->R) / 2)
Insert(pRoot->pLeft,i,v);
else
Insert(pRoot->pRight,i,v);
}
void Query(CNode * pRoot,int s,int e)
{
if( pRoot->nMin >= nMin && pRoot->nMax <= nMax )
return;
if( s == pRoot->L && e == pRoot->R) {
nMin = _cpp_min(pRoot->nMin,nMin);
nMax = _cpp_max(pRoot->nMax,nMax);
return ;
}
if( e <= (pRoot->L + pRoot->R) / 2 )
Query(pRoot->pLeft, s,e);
else if( s >= (pRoot->L + pRoot->R) / 2 + 1)
Query(pRoot->pRight, s,e);
else {
Query(pRoot->pLeft, s,(pRoot->L + pRoot->R) / 2);
Query(pRoot->pRight, (pRoot->L + pRoot->R) / 2 + 1 ,e);
}
}
int main()
{
int n,q,v;
int i;
cin>>n>>q;
nCount = 0;
BuildTree(Tree,1,n);
for(i = 1;i <= n;i++)
{
scanf("%d",&v);
Insert(Tree,i,v);
}
for(i = 0;i < q;i++)
{
int s,e;
scanf("%d %d",&s,&e);
nMin = MY_MIN;
nMax = MY_MAX;
Query(Tree,s,e);
printf("%d\n",nMax - nMin);
}
return 0;
}
2.POJ 3468 A Simple Problem with Integers
给定Q(1 ≤ Q≤ 100,000)个数A1,A2… AQ,,以及可能多次进行的两个操作:1)对某个区间Ai … Aj的个数都加n(n可变)2) 求某个区间Ai … Aj的数的和
只存和,会导致每次加数的时候都要更新到叶子节点,速度太慢,这是必须要避免的。
#include <iostream>
using namespace std;
struct CNode
{
int L ,R;
CNode * pLeft, * pRight;
__int64 nSum; //原来的和
__int64 Inc; //增量c的累加
};
CNode Tree[10000000];
int nCount = 0;
int Mid( CNode * pRoot)
{
return (pRoot->L + pRoot->R)/2;
}
void BuildTree(CNode * pRoot,int L, int R)
{
pRoot->L = L;
pRoot->R = R;
pRoot->nSum = 0;
pRoot->Inc = 0;
if( L == R)
return;
nCount ++;
pRoot->pLeft = Tree + nCount;
nCount ++;
pRoot->pRight = Tree + nCount;
BuildTree(pRoot->pLeft,L,(L+R)/2);
BuildTree(pRoot->pRight,(L+R)/2+1,R);
}
void Insert( CNode * pRoot,int i, int v)
{
if( pRoot->L == i && pRoot->R == i) {
pRoot->nSum = v;
return ;
}
pRoot->nSum += v;
if( i <= Mid(pRoot))
Insert(pRoot->pLeft,i,v);
else
Insert(pRoot->pRight,i,v);
}
void Add( CNode * pRoot, int a, int b, __int64 c)
{
if( pRoot->L == a && pRoot->R == b) {
pRoot->Inc += c;
return ;
}
pRoot->nSum += c * ( b - a + 1) ;
if( b <= (pRoot->L + pRoot->R)/2)
Add(pRoot->pLeft,a,b,c);
else if( a >= (pRoot->L + pRoot->R)/2 +1)
Add(pRoot->pRight,a,b,c);
else {
Add(pRoot->pLeft,a,(pRoot->L + pRoot->R)/2 ,c);
Add(pRoot->pRight,(pRoot->L + pRoot->R)/2 + 1,b,c);
}
}
__int64 QuerynSum( CNode * pRoot, int a, int b)
{
if( pRoot->L == a && pRoot->R == b)
return pRoot->nSum + (pRoot->R - pRoot->L + 1) * pRoot->Inc ;
pRoot->nSum += (pRoot->R - pRoot->L + 1) * pRoot->Inc ;
Add( pRoot->pLeft,pRoot->L,Mid(pRoot),pRoot->Inc);
Add( pRoot->pRight,Mid(pRoot) + 1,pRoot->R,pRoot->Inc);
pRoot->Inc = 0;
if( b <= Mid(pRoot))
return QuerynSum(pRoot->pLeft,a,b);
else if( a >= Mid(pRoot) + 1)
return QuerynSum(pRoot->pRight,a,b);
else {
return QuerynSum(pRoot->pLeft,a,Mid(pRoot)) +
QuerynSum(pRoot->pRight,Mid(pRoot) + 1,b);
}
}
int main()
{
int n,q,a,b,c;
char cmd[10];
scanf("%d%d",&n,&q);
int i,j,k;
nCount = 0;
BuildTree(Tree,1,n);
for( i = 1;i <= n;i ++ ) {
scanf("%d",&a);
Insert(Tree,i,a);
}
for( i = 0;i < q;i ++ ) {
scanf("%s",cmd);
if ( cmd[0] == 'C' ) {
scanf("%d%d%d",&a,&b,&c);
Add( Tree,a,b,c);
}
else {
scanf("%d%d",&a,&b);
printf("%I64d\n",QuerynSum(Tree,a,b));
}
}
return 0;
}
#include <iostream>
#include <algorithm>
using namespace std;
int n;
struct CPost
{
int L,R;
};
CPost posters[10100];
int x[20200];
int hash[10000010];
struct CNode
{
int L,R;
bool bCovered; //本区间是否已经被完全覆盖
CNode * pLeft, * pRight;
};
CNode Tree[100000];
int nNodeCount = 0;
int Mid( CNode * pRoot)
{
return (pRoot->L + pRoot->R)/2;
}
void BuildTree( CNode * pRoot, int L, int R)
{
pRoot->L = L;
pRoot->R = R;
pRoot->bCovered = false;
if( L == R )
return;
nNodeCount ++;
pRoot->pLeft = Tree + nNodeCount;
nNodeCount ++;
pRoot->pRight = Tree + nNodeCount;
BuildTree( pRoot->pLeft,L,(L+R)/2);
BuildTree( pRoot->pRight,(L+R)/2 + 1,R);
}
bool Post( CNode *pRoot, int L, int R)
{
if( pRoot->bCovered )
return false;
if( pRoot->L == L && pRoot->R == R) {
pRoot->bCovered = true;
return true;
}
bool bResult ;
if( R <= Mid(pRoot) )
bResult = Post( pRoot->pLeft,L,R);
else if( L >= Mid(pRoot) + 1)
bResult = Post( pRoot->pRight,L,R);
else {
bool b1 = Post(pRoot->pLeft ,L,Mid(pRoot));
bool b2 = Post( pRoot->pRight,Mid(pRoot) + 1,R);
bResult = b1 || b2;
}
//要更新根节点的覆盖情况
if( pRoot->pLeft->bCovered && pRoot->pRight->bCovered )
pRoot->bCovered = true;
return bResult;
}
int main()
{
int t;
int i,j,k;
scanf("%d",&t);
int nCaseNo = 0;
while(t--) {
nCaseNo ++;
scanf("%d",&n);
int nCount = 0;
for( i = 0;i < n;i ++ ) {
scanf("%d%d", & posters[i].L,& posters[i].R );
x[nCount++] = posters[i].L;
x[nCount++] = posters[i].R;
}
sort(x,x+nCount);
nCount = unique(x,x+nCount) - x; //去掉重复元素
for( i = 0;i < nCount;i ++ )
hash[x[i]] = i;
nNodeCount = 0;
BuildTree( Tree,0,nCount - 1);
int nSum = 0;
for( i = n-1;i >= 0;i -- ) { // 从后往前看板是否看得见
if( Post(Tree,hash[posters[i].L],hash[posters[i].R]))
nSum ++;
}
printf("%d\n",nSum);
}
return 0;
}