(线段树入门)
HDU 1166 敌兵布阵(单点更新)(结构体形式)
题目意思:输入T,T个测试样例
每一次输入一个n,说明有n个数据。再输入n个数据。
输入Query a b :查询a b 之间的和
输入Add i x:在 i 处增加 x个数
输入Sub i x:在 i 处删除 x个数
输入End 结束
举例:
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3: 输出 6(1+2+3)
Add 3 6: 数组变为1 2 9 4 5 6 7 8 9 10
Query 2 7: 输出33(2+9+4+5+6+7)
Sub 10 2: 数组变成1 2 9 4 5 6 7 8 9 8
Add 6 3: 数组变成1 2 9 4 5 9 7 8 9 8
Query 3 10:输出59(9+4+5+9+7+8+9+8)
End : 结束
建树: 线段树的优点在于:★将可能的和提前算好(如:要算结点16,17,9,10,11的和,那么只用输出结点2那么就是了,减少了查询时相加的过程)
55(1)
/ \
15(2) 40(3)
/ \ / \
6(4) 9(5) 21(6) 19(7)
/ \ / \ / \ / \
3(8) 3(9) 4(10) 5(11) 13(12) 8(13) 9(14) 10(15)
/ \ 【3】 【4】 【5】 / \ 【8】 【9】 【10】
1(16) 2(17) 6(18) 7(19)
【1】 【2】 【6】 【7】
tip:()中结点编号,【】中为范围下标,普通的就是输入的数值和部分范围内的和
每棵树结点:
#define ll rt<<1
#define rr rt<<1|1
using namespace std;
const int maxn=50010;
struct Tree{
int l,r;//sum值包含的 最左边界 和 最右边界
int sum;
}tree[maxn<<2];
①建线段树:
void PushUP(int rt){
tree[rt].sum= tree[ll].sum +tree[rr].sum;
}
void build(int l,int r,int rt)
{
tree[rt].l=l; tree[rt].r=r;
if(l==r){
scanf("%d",&tree[rt].sum);
return ;
}
int m=(l+r)>>1;
build(l,m,ll); //ll -> #define ll rt<<1
build(m+1,r,rr);//rr -> #define rr rt<<1|1
PushUP(rt);
}
输入建树的范围最左值(默认从1开始),最右值(输入的n),和树的总根结点(默认以1为总根结点)
build(1,n,1);tree[ rt ].l = l ; tree[ rt ].r = r ;
总的根节点 rt = 1(1)。那么tree[1]最左为1,最右为n,意思是:结点(1)所包含的和是从1到n的
然后分别向左右两边差分建树。
build( l , m , rt * 2 );
左边( 1,(n+1)/2 )
那么左边的根节点为 rt = 2*rt =2(2) 。那么tree[2]最左为1,最右为(n+1)/2,意思是:结点(2)所包含的和是从1到 n/2 的
继续往左右建树 ……
build( m+1 , r , rt * 2+1 );
右边类似。
PushUP( rt );
执行完上面两步之后,根节点rt 已经有 左右节点了,那么rt 的值就为 rt * 2与 rt * 2 + 1的和
if ( l == r )
直到 l == r,意思是范围缩小到一个点,那么在这个点输入值。
★为什么建树 最底层的叶子节点 是值 1 ,2 ,6 ,7而不是1 ,2 ,3 ,4 ?
因为树建立过程是折半的,将 数组[ 10 ]= 1 2 3 4 5 6 7 8 9 10
分成两半,一半 1 2 3 4 5 ,一半 6 7 8 9 10。
然后继续分成两边。分到底的时候,将1,2放到最底部,然后往上。
(取巧)因为一半一半,12345 和678910是等效的。所以最底层叶子节点的值为1,2,和6,7
★左右建树的顺序能换吗?
不能,输入的顺序从左到右,所以要优先往左边探,折半探索到左边的底,开始输入第一个数,然后依次探索到底,依次输入其他数。这样就是按顺序到树里了。
左右建树的顺序换了之后,那输入的数就不是原来的顺序了(就乱了)
★数值比较多,再帮忙屡一下,分为三类:
(1)rt 这个值是结点编号
(2)tree[ rt ].sum 这个值是 输入的值,和输入值的部分和
(3)tree[ rt ].l ,tree[ rt ].r 的值是左右范围,即1~n的数
②更新操作
void update(int p,int add,int rt)
{
if( tree[rt].l == tree[rt].r ) {
tree[rt].sum += add;
return ;
}
int m = ( tree[rt].l + tree[rt].r ) >>1;
if(p <= m) update( p, add, ll );
else update( p, add, rr );
PushUP(rt);
}
输入:要改的值的位置p,要增加的数值add,总根结点rt(默认1)
当根节点rt 的左右范围值相等时,那么就是该查询的点了。根节点 这个值 +add如果要更新的那个值所在的位置 比 rt的左右范围的中间值小,那么要更新位置在 rt 的 左边,反之在右边
做完上面的更新之后,然后重新计算和值。
③查询操作
int query(int L,int R,int rt)
{
if(L <= tree[rt].l && tree[rt].r <= R)
return tree[rt].sum;
int m = ( tree[rt].l + tree[rt].r )>>1;
if(R <= m) return query(L,R,ll);
else if(L > m) return query(L,R,rr);
else return query(L,R,ll) + query(L,R,rr);
}
输入:要查询的左右范围,L~R(包括L,R)和总根结点rt
m
( tree[ rt ].l ) |_______|_______|( tree[ rt ].r)
(L)|_____________________|(R)
【状态一(左边和右边都还剩下一点没包含到里面,可能是是下面其他状态,然后用状态四整合)】
然后再看如果R 比 m小
m
( tree[ rt ].l ) |________|________|( tree[ rt ].r)
( tree[ rt * 2 ].l ) |________|( tree[ rt * 2 ].r )
(L)|_____________________|(R)
【状态二:向左缩小范围(此图只考虑右边部分,左边部分可能是其他状态,然后用状态四整合)】
如果L 比m大m
( tree[ rt ].l ) |______|______|( tree[ rt ].r)
( tree[ rt * 2 +1 ].l ) |______|( tree[ rt * 2 +1 ].r)
(L)|_____________________|(R)
【状态三:向右缩小范围(此图只考虑左边部分,右边部分可能是其他状态,然后用状态四整合)】
其他情况:m m
( tree[ rt ].l ) |______________|_____________|( tree[ rt ].r) ( tree[ rt ].l ) |________|________|( tree[ rt ].r)
|______________||_____________| |________||________|
(L)|_____________________|(R) (L)|_____________________|(R)
【状态四:需要左右相加】 【状态四的子状态(还有右边的情况就不画了)】可能有人问还有其他状态:
m
( tree[ rt ].l ) |______|______|( tree[ rt ].r)
(L)|_____________________|(R)
★如果这样呢,这样和【状态三】不是一样吗?答:这部分不存在,我们不考虑这部分。因为一开始范围肯定是最大的1~n,包含了所有情况,那么就是【状态四】,不断的缩小到我们要的部分。
有这种情况,但这个情况正好是【状态二】和【状态三】所丢弃的部分,且看第二行,我们的折半操作,另一半已经丢掉不要了。
④主函数:
int main()
{
int cas=1,T,n;
scanf("%d",&T);
while(T--)
{
printf("Case %d:\n",cas++);
scanf("%d",&n);
build(1,n,1);
/*
for(int i=1;i<=2*n;i++){
printf("%d %d %d\n",tree[i].l , tree[i].sum , tree[i].r);
}
查看建树
for(int i=1;i<=3*n;i++)
printf("%d--",sum[i]);*/
char op[10];
while(scanf("%s",op)){
if(op[0]=='E') //结束
break;
int a,b;
scanf("%d%d",&a,&b);
if(op[0]=='Q') //询问
printf("%d\n",query(a,b,1));
else if(op[0]=='S') //减去
update(a,-b,1);
else if(op[0]=='A') //增加
update(a,b,1);
}
}
return 0;
}