强烈推荐:这篇文章------------------------(完全版)线段树。
线段树的作用:解决区间计算问题。
例如:记录一个区间的最值(最大或最小值)和总量,并在区间的插入、删除、修改中维护这些最值和总量。
线段树是一颗二叉树。记为T(a,b),a,b表示顶点T为区间[a,b]。区间长度b-a记为L。递归定义T[a,b]:
若区间长度L>=1,区间[a,(a+b)/2]为T的左孩子,区间((a+b)/2,b]为T的右孩子。
如图为区间[1,10]的线段树:
线段树的基本操作:
(1)建立线段树T(a,b)。
(2)将区间[c,d]插入到线段树T(a,b)。
(3)将区间[c,d]从线段树T(a,b)中删除。
(4)对线段树进行动态维护。
以下代码转载自:himdd的文章。
线段树的建立:
int Pushup(int root) //把当前结点的信息更新到父节点
{ sum[root]=sum[root<<1]+sum[root<<1|1];
}
void Build(int L,int R,int root)
{ if(L==R)
{ scanf("%d",&sum[root]);
return ;
}
int mid=(L+R)>>1;
Build(Lson); //左孩子
Build(Rson); //右孩子
Pushup(root);
}
上面代码构造的树为,我没有画完整,但是已经能够体现问题了。
后面的数相当与这颗树的根,区间[1,10]的根设为1,所以[1,5]的就为1<<1=2,[6,10]的根为:(1<<1)+1=3,依次类推,然后用sum[root]表示根节点为root的所代表的这个区间的值,如:sum[1]保存的就是[1,10]这个区间的值。所以有:sum[1]=sum[2]+sum[3],即:sum[root]=sum[root>>1]+sum[(root>>1)+1]。
线段树的修改:比方说我要修改某一个结点的值,那么我只需要沿着根结点1往下寻找,并记录这条路径,更新sum的值即可,比方说变化2:它需要修改的区间依次为:[1,10]
->[1,5]->[1,3]->[1,2]->[2,2]。具体操作见代码:
void Update(int q,int val,int L,int R,int root) //在根为root,区间为[L,R]中的线段树修改结点p的值增加val
{ if(L==R)
{ sum[root]+=val;
return ;
}
int mid=(L+R)>>1;
if(q<=mid) Update(q,val,Lson); //说明p在左结点
else Update(q,val,Rson); //说明p在右结点
Pushup(root);
}
至于统计某一区间[a,b]的值,那么同样可以利用二分的思想:
比方说要求[4,8],二分mid=(4+8)/2=6,然后它就可以写成:[4,6]+[7,8]=[4,5]+[6,6]+[7,7]+[8,8]=[4,4]+[5,5]+...+[8,8]。代码见下题中的Query部分。
HDU 1166(敌兵布阵),相当于树状数组中的插点问线,初始化给你每个结点的值,然后更新每个点的值,求给定的任意一个区间的值。
#include
#include
#include
using namespace std;
const int MAX=50010;
#define Lson L,mid,root<<1 //遇到Lson的时候强制替换为后面的语句
#define Rson mid+1,R,root<<1|1
int n,sum[MAX<<2];
int Pushup(int root) //把当前结点的信息更新到父节点
{ sum[root]=sum[root<<1]+sum[root<<1|1];
}
void Build(int L,int R,int root)
{ if(L==R)
{ scanf("%d",&sum[root]);
return ;
}
int mid=(L+R)>>1;
Build(Lson); //左孩子
Build(Rson); //右孩子
Pushup(root);
}
void Update(int q,int val,int L,int R,int root) //在根为root,区间为[L,R]中的线段树修改结点p的值增加val
{ if(L==R)
{ sum[root]+=val;
return ;
}
int mid=(L+R)>>1;
if(q<=mid) Update(q,val,Lson); //说明p在左结点
else Update(q,val,Rson); //说明p在右结点
Pushup(root);
}
int Query(int ql,int qr,int L,int R,int root) //在根为root,区间为[L,R]的线段树中,计算区间[ql,qr]的和
{ if(ql<=L && R<=qr) return sum[root];
int mid=(L+R)>>1; //利用二分的思想
int res=0;
if(ql<=mid) res+=Query(ql,qr,Lson);
if(qr>mid) res+=Query(ql,qr,Rson);
return res;
}
int main()
{ int a,b,Case,num=1;
scanf("%d",&Case);
while(Case--)
{ printf("Case %d:\n",num++);
scanf("%d",&n);
Build(1,n,1);
char op[10];
while(scanf("%s",op))
{ if(op[0]=='E') break;
scanf("%d%d",&a,&b);
if(op[0]=='A') Update(a,b,1,n,1);
if(op[0]=='S') Update(a,-b,1,n,1);
if(op[0]=='Q') printf("%d\n",Query(a,b,1,n,1));
}
}
return 0;
}