线段树
操作:
区间分解(LogN):
如果有某个节点代表的区间完全属于待分解区间,则该节点为“终止节点” 不再继续向下分解。
所有终止节点不重叠,加在一起恰等于整个待分解区间。
伪代码:
function (根节点pRoot,查询区间[l,r]){
if(查询区间恰等于节点表示区间)
return;
if(查询区间都在节点中点左侧)
function(pRoot左儿子,查询区间[l,r]);
else if(查询区间都在节点中点左侧)
function(pRoot右儿子,查询区间[l,r]);
else {
function(pRoot左儿子,查询区间[l,mid]);
function(pRoot右儿子,查询区间[mid+1,r]);
}
}
poj3264
本题中线段树的每个节点要记录该区间内的最大值和最小值。
在查询时,采用区间分解的操作,取得整个查询区间的最值。
#include<iostream>
#include<algorithm>
using namespace std;
#define MY_MIN 99999999
#define MY_MAX -99999999
struct CNode{
int L,R;
int nMin,nMax;
CNode *pLeft ,* pRight;
};
int nMax,nMin;
CNode Tree[1000000];
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)
//将第i个数,其值为v,插入线段树
{
if( pRoot->L == i && pRoot->R == i ) {
pRoot->nMin = pRoot->nMax = v;
return;
}
pRoot->nMin = min(pRoot->nMin,v);
pRoot->nMax =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)
//查询区间[s,e]中的最小值和最大值,如果更优就记在全局变量里
{
if( pRoot->nMin >= nMin && pRoot->nMax <= nMax )
return;
if( s == pRoot->L && e == pRoot->R) {
nMin = min(pRoot->nMin,nMin);
nMax =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,h;
int i,j,k;
scanf("%d%d",&n,&q);
nCount = 0;
BuildTree(Tree,1,n);
for( i = 1;i <= n;i ++ ) {
scanf("%d",&h);
Insert( Tree,i,h);
}
for( i = 0;i < q;i ++ ) {
int s,e;
scanf("%d%d", &s,&e);
nMax = MY_MAX;
nMin = MY_MIN;
Query(Tree,s,e);
printf("%d\n",nMax - nMin);
}
return 0;
}
poj3468
本题有两个操作。
1.对区间内所有数加n
2.对区间求和
本题中每个节点分别维护Sum和Inc
Inc作为增量的表示,即真正的和等于Sum+len*Inc (len为节点所表示区间长度)
#include <iostream>
using namespace std;
struct CNode
{
int L ,R;
CNode * pLeft, * pRight;
long long nSum; //原来的和
long long Inc; //增量c的累加
};
CNode Tree[1000000];
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, long long 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);
}
}
long long 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;
}
poj2528
本题若以瓷砖端点为区间建立线段树,肯定会MLE
所以要用到离散化的方法。
以海报端点建立区间。
Insert的过程就是区间分解的过程。
#include<iostream>
#include<algorithm>
#include<cmath>
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[1000000];
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;
scanf("%d",&t);
int nCaseNo = 0;
while(t--){
nCaseNo ++;
cin>>n;
int nCount=0;
for(int i=0;i<n;i++){
cin>>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;
int nIntervalNo=0;
for(int i=0;i<nCount;i++){
hash[x[i]]=nIntervalNo;
if(i<nCount-1){
if(x[i+1]-x[i]==1)
nIntervalNo++;
else nIntervalNo+=2;
}
}
BuildTree(Tree,0,nIntervalNo);
int nSum=0;
for(int i=n-1;i>=0;i--){
if(Post(Tree,hash[posters[i].L],hash[posters[i].R]))
nSum++;
}
cout<<nSum<<endl;
}
return 0;
}