学习线段树
线段树的建树
建树的写法根据题目要求来写,总体差距不大,但是要真正理解线段树才能写对
struct SegmentTree { int l,r; //l代表t[p]的左点 int dat; //等等 }t[size*4];
void build(int p,int l,int r) { t[p].l=l,t[p].r=r; if(l==r) { t[p].dat=a[l] ;return ;} int mid=(l+r)/2; build(p*2,l,mid); //从根节点寻找到叶节点 build(p*2+1,mid+1,r); //这里是根据区间最大值为例来建树的 t[p].dat=max(t[p*2].dat,t[p*2+1].dat); //从叶节点到根节点更新数 }
线段树的单点修改
单点修改没什么特别之处
void change(int p,int x,int v) { if(t[p].l==t[p].r){t[p].dat=v;return;} int mid=(t[p].l+t[p].r)/2; if(x<=mid) change(p*2,x,v); else change(p*2+1,x,v); t[p].dat=max(t[p*2].dat,t[p*2+1].dat); //从下往上更新信息 }
线段树的区间查询
int ask(int p,int l,int r) { if(l<=t[p].l && r>=t[p].r) return t[p].dat; //这里是包含关系,不理解的话可能会擅自改成等于 int mid = (t[p].l + t[p].r)/2; int val=-(1<<30); //负无穷大 if(l<=mid) val=max(val,ask(p*2,1,r)); if(r>mid) val=max(val,ask(p*2+1,1,r)); return val; }
延迟标记 (线段树的特色)
如果题目要求区间修改而不是单点修改,如果当你将这个区间每个都单点修改,时间会很复杂,所以用到延时标记。
如果把区间里每个都单点修改,当在后面查询的时候有些只会用到某些根节点,而那些叶节点就白白更新了,浪费了时间。所以可以修改的时候标记,后面查询的时候在进行修改。
POJ 3468
void build(ll p,ll l,ll r) { l(p) = l,r(p)=r; if(l == r) {sum(p)=a[l] ;return ;} ll mid=(l+r)/2; build(p*2, l,mid); build(p*2+1 , mid+1,r); sum(p)+=sum(p*2)+sum(p*2+1); } void spread(ll p) { //cout<<p<<" "<<add(p)<<endl; if(add(p)) { sum(p*2)+=add(p) * (r(p*2)-l(p*2)+1); sum(p*2+1)+= add(p) * (r(p*2+1)-l(p*2+1)+1); add(p*2)+=add(p); add(p*2+1)+=add(p); add(p)=0; } } void change(ll p,ll l,ll r,ll d ) { if(l<=l(p) && r>=r(p)) { sum(p)+=(ll )d * (r(p)-l(p)+1); add(p) +=d; return ; } spread(p); ll mid = (l(p)+r(p))/2; if(l<=mid) change(p*2 , l, r, d); if(r>mid) change(p*2+1,l,r,d); sum(p) = sum(p*2)+sum(p*2+1); } ll ask(ll p,ll l,ll r) { cout<<p<<" ..."<<endl; if(l<=l(p) && r>=r(p) ) return sum(p); spread(p); ll mid = (l(p)+r(p))/2; ll val=0; if(l<=mid) val+=ask(p*2,l,r); if(r>mid) val+=ask(p*2+1,l,r); return val; }