一、建树
建树:
二分法
ll ls(ll p){ return p<<1 ; }
ll rs(ll p){ return p<<1|1 ; }
void push_up(ll p){ ans[p] = ans[ls(p)]+ans[rs(p)] ; }
void build(ll p, ll l , ll r)
{
if(l == r)
{
ans[p] = a[l] ;
return ;
}
ll mid = (l+r)>>1 ;
build(ls(p),l,mid) ;
build(rs(p),mid+1,r) ;
push_up(p) ;
}
二、懒惰标记
可供区间修改与区间查询使用。
tag[]思路:先push_down往下传之前标记。若当前区间被完全覆盖,则做标记tag[],不继续往下递归,减少时间复杂度。注意tag不需回溯。
ans[]思路:建立时从上往下找到完全覆盖区间进行标记,再向上回溯(push_up)。
以下两函数仅用于懒惰标记。
f函数为操作。
void f(ll p , ll l , ll r ,ll k)
{
ans[p] += (r-l+1)*k ;
tag[p] += k ;
}
push_down作用为向下传一层标记,
void push_down(ll p, ll l , ll r)
{
ll mid = (l+r)>>1 ;
f(ls(p),l,mid,tag[p]) ;
f(rs(p),mid+1,r,tag[p]) ;
tag[p] = 0 ;
}
三、区间询问与区间修改
区间询问:
二分法思路:二分向下寻找到完全覆盖的当前区间。
若当前区间被完全覆盖,则返回res。
if(nl <= l && r <= nr)
否则继续向下判断查找。
if(nl <= mid)
if(mid < nr)
懒惰标记思路:
若未找到完全覆盖的区间,则往下传一层懒惰标记。即更新当前区间值,因此懒惰标记可以清零。
完整代码:
ll query(ll p , ll nl , ll nr , ll l , ll r)
{
//cout << "query" << p <<" " << l <<" " << r << endl ;
ll res = 0 ;
if(nl <= l && r <= nr)
{
//cout << "ans[" << p << "]= " << ans[p] << endl ;
return ans[p] ;
}
push_down(p,l,r) ;
ll mid = (l+r)>>1 ;
if(nl <= mid) res += query(ls(p) , nl , nr , l , mid ) ;
if(mid < nr) res += query(rs(p) , nl ,nr , mid+1, r ) ;
return res ;
}
区间修改:
区间修改:
二分思路:与查询相似。二分法从上往下找到完全覆盖的区间(即均要修改或查询的各区间)进行操作。
懒惰标记思路:每次向下一层先传当层懒惰标记,再进行update操作。
void update(ll p, ll nl, ll nr, ll l, ll r, ll k)
{
if(nl <= l && r <= nr)//找到覆盖区间,进行操作
{
f(p,l,r,k) ;
return ;
}
push_down(p,l,r) ;
ll mid = (l+r)>>1 ;
if(nl <= mid) update(ls(p) , nl , nr , l , mid , k ) ;
if(mid < nr) update(rs(p) , nl ,nr , mid+1, r , k ) ;
push_up(p) ;
}
单点修改:
二分查找到需要更改的点进行修改。
因为是从上往下进行修改,所以需要回溯。
push_up(p) ;
完整代码:
void update(ll p , ll l , ll r , ll x , ll k )//二分找点
{
if(l == r)
{
ans[p] += k ;
return ;
}
ll mid = (l+r)>>1 ;
if(x <= mid ) update(ls(p),l,mid,x,k) ;
else update(rs(p),mid+1,r,x,k) ;
push_up(p) ;
}
四、做的练习题
单点修改: A - 敌兵布阵 HDU - 1166
区间修改: C - A Simple Problem with Integers POJ - 3468
五、总结
1、学会用输出来代替debug,看整个动态过程,收获大。
2、写博客可以扣每个细节,发现很多之前以为没有问题的点(如建树)。
3、递推递归需要做下练习。
4、需要腾更多时间敲代码。