您的位置:首页 > 其它

总结之线段树1

2016-07-27 14:33 381 查看
线段树是利用了完全二叉树的一种数据结构,在每个节点保存一条线段,可以维护和询问一组数据,主要用于解决一段数据的动态查询问题。线段树需要维护的是子叶和根之间的变化关系并且可以查询,在预处理时耗时O(n),查询,更新操作需要o(logn),空间消耗o(n)。

线段树单点更新,每次修改对应子叶节点,相应的根节点也要改变,如hduoj 1754 I Hate It (点击打开题目链接

题解博客:(点击打开题解链接

线段树区间更新是难点,由于更新一段区间如果每个节点都去更新很容易超时,可以引入一个延迟变量(懒惰变量lazy),用来标记是否需要对子叶进行更改,再在下一次修改或查询时进行更新以减少操作降低时间。

区间更新题型:加上某值(add k) poj 3468 A Simple Problem with Integers (点击打开题目链接

题意:有一段数,有两种操作,一:某一区间所有值加上某数。二:输出某一区间所有值的和。

#include<stdio.h>
#define LL long long
int n,m;
struct tree{
int l,r;
LL v,m;
}t[400100];
void build(int k,int l,int r){//构建线段树
t[k].l=l,t[k].r=r,t[k].m=0;
if(l==r){
scanf("%lld",&t[k].v);
return ;
}
int mid=(t[k].l+t[k].r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
t[k].v=t[k*2].v+t[k*2+1].v;
}
void pushup(int k){//子叶节点向上更新
t[k].v=t[k*2].v+t[k*2+1].v;
}
void pushdown(int k){//根节点标记向下更新
if(t[k].m){
t[k*2].m+=t[k].m;
t[k*2].v+=t[k].m*(t[k*2].r-t[k*2].l+1);
t[k*2+1].m+=t[k].m;
t[k*2+1].v+=t[k].m*(t[k*2+1].r-t[k*2+1].l+1);
t[k].m=0;
}
}
LL query(int k,int l,int r){//查询某区间和函数
if(t[k].l>=l&&r>=t[k].r)  return t[k].v;
pushdown(k);
int mid=(t[k].l+t[k].r)/2;
LL t1=0,t2=0;
if(mid>=l) t1=query(k*2,l,r);
if(mid<r)  t2=query(k*2+1,l,r);
return t1+t2;
}
void updata(int k,int l,int r,LL add){//更新线段树
int mid=(t[k].l+t[k].r)/2;
if(t[k].l>=l&&r>=t[k].r){//当前的线段在查询区间内,则此线段和增加(r-l+1)*add,并做上标记
t[k].m+=add;
t[k].v+=(t[k].r-t[k].l+1)*add;
return ;
}
pushdown(k);
if(mid>=l) updata(k*2,l,r,add);
if(mid<r) updata(k*2+1,l,r,add);
pushup(k);
}
int main(){
scanf("%d%d",&n,&m);
build(1,1,n);
while(m--){
char str[10];
int l,r,x;
scanf("%s",str);
if(str[0]=='Q'){
scanf("%d%d",&l,&r);
printf("%lld\n",query(1,l,r));
}
if(str[0]=='C'){
scanf("%d%d%lld",&l,&r,&x);
updata(1,l,r,x);
}
}
return 0;
}


区间更新题型:将某区间变为某数(set k) hduoj 1698 Just a Hook (点击打开链接

题意:有n的数开始全为1,现在改变一段区间为1,2或3,进行m此操作,最后输出总长度。

#include<stdio.h>
int n;
struct tree{
int l,r,m,v;
}t[400010];
void pushdown(int k){//向下传递将值置为add
if(t[k].m){
t[k*2].m=t[k].m;
t[k*2].v=(t[k*2].r-t[k*2].l+1)*t[k].m;
t[k*2+1].m=t[k].m;
t[k*2+1].v=(t[k*2+1].r-t[k*2+1].l+1)*t[k].m;
t[k].m=0;
}
}
void pushup(int k){
t[k].v=t[k*2].v+t[k*2+1].v;
}
void build(int k,int l,int r){
t[k].l=l,t[k].r=r,t[k].m=0;
if(t[k].r==t[k].l){
t[k].v=1;
return ;
}
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
pushup(k);
}
void updata(int k,int l,int r,int add){
if(t[k].l>=l&&t[k].r<=r){
t[k].m=add;
t[k].v=add*(t[k].r-t[k].l+1);
return ;
}
pushdown(k);
int mid=(t[k].r+t[k].l)/2;
if(mid>=l)  updata(k*2,l,r,add);
if(mid<r)   updata(k*2+1,l,r,add);
pushup(k);
}
int main(){
int m,ca,num=1;
scanf("%d",&m);
while(m--){
scanf("%d%d",&n,&ca);
build(1,1,n);
while(ca--){
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
updata(1,l,r,x);
}
printf("Case %d: The total value of the hook is %d.\n",num++,t[1].v);//不需要询问,每次只要输出根节点的sum就行
}
return 0;
}


hduoj 4027 Can you answer these queries? (点击打开链接)
题意:给出n个数和m次操作,操作有两种,1:将某个区间的所有值全部开方 2:输出区间值之和。由于开方,只能标记开方次数并且只有对最底层的子叶进行开方操作。

#include<stdio.h>
#include<math.h>
#define LL long long
int n;
LL a[100010];
struct tree{
int l,r;
LL v;
}t[400100];
void build(int k,int l,int r){
t[k].l=l;t[k].r=r;
if(l==r){
scanf("%lld",&t[k].v);
return ;
}
t[k].v=0;
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
t[k].v=t[k*2].v+t[k*2+1].v;
}
void updata(int k,int l,int r){
if(t[k].v==t[k].r-t[k].l+1)   return ;//这一段全为1,开方操作不影响
if(t[k].l==t[k].r){
t[k].v=sqrt(double(t[k].v));
return ;
}
int mid=(t[k].l+t[k].r)/2;
if(l<=mid) updata(k*2,l,r);
if(r>mid)   updata(k*2+1,l,r);
t[k].v=t[k*2].v+t[k*2+1].v;
}
LL query(int k,int l,int r){
if(t[k].l>=l&&t[k].r<=r)    return t[k].v;
int mid=(t[k].l+t[k].r)/2;
LL t1=0,t2=0;
if(mid>=l) t1=query(k*2,l,r);
if(mid<r)  t2=query(k*2+1,l,r);
return t1+t2;
}
int main(){
int ca=1;
while(~scanf("%d",&n)){
int m;
build(1,1,n);
scanf("%d",&m);
printf("Case #%d:\n",ca++);
while(m--){
int x,i,j;
scanf("%d%d%d",&x,&i,&j);
if(i>j) {i+=j;j=i-j;i=i-j;}
if(x==0){
updata(1,i,j);
}
else{
printf("%lld\n",query(1,i,j));
}
}
printf("\n");
}
return 0;
}


线段树最大连续区间:hduoj 1540 Tunnel Warfare (点击打开链接)

题意:给出n个数和m次操作,开始n个数可以全看为1并且长度都为n,有三种操作:D:破坏此村庄。Q:输出与此村庄联通的村庄数目。R:恢复最近一次破坏的村庄。区间长度在树中存下:此节点中最大的值sum,从左边界点向右延伸的点ll,从右边界点向左延伸的点rl。

#include<stdio.h>
#include<algorithm>
using namespace std;
int n,m;
int stk[50010],top;//用数组和top模拟栈,用来存下毁灭村庄的操作顺序
struct tree{
int l,r;
int len,ll,rl;
}t[200010];
void build(int k,int l,int r){
t[k].l=l,t[k].r=r;
t[k].ll=t[k].rl=t[k].len=r-l+1;//开始时长度都为r-l+1
if(l==r) return ;
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
}
void updata(int k,int l,int add){//add为1时是修复操作,否则是毁灭操作
if(t[k].l==t[k].r){
if(add==1)  t[k].ll=t[k].rl=t[k].len=1;
else t[k].ll=t[k].rl=t[k].len=0;
return ;
}
int mid=(t[k].r+t[k].l)/2;
if(l<=mid)  updata(k*2,l,add);
else updata(k*2+1,l,add);
if(t[k*2].ll==t[k*2].r-t[k*2].l+1)  t[k].ll=t[k*2].ll+t[k*2+1].ll;
else t[k].ll=t[k*2].ll;
if(t[k*2+1].rl==t[k*2+1].r-t[k*2+1].l+1)    t[k].rl=t[k*2+1].rl+t[k*2].rl;//更新节点的ll,rl
else t[k].rl=t[k*2+1].rl;
t[k].len=max(t[k*2].len,t[k*2+1].len);//根节点最大长度为子节点最大长度取max
t[k].len=max(max(t[k].rl,t[k].ll),t[k].len);//若是左
4000
右孩子节点中间有连接部分,取max
}
int query(int k,int l){
if(t[k].l==t[k].r||t[k].len==0||t[k].len==t[k].r-t[k].l+1) return t[k].len;
int mid=(t[k].l+t[k].r)/2;
if(mid>=l){
if(l>=t[k*2].r-t[k*2].rl+1) return t[k*2].rl+t[k*2+1].ll;//如果查询编号在左右孩子节点的连接部分上,需要返回左rl+右ll
else return query(k*2,l);//否则只在左孩子上
}
else{
if(l<=t[k*2+1].l+t[k*2+1].ll-1) return t[k*2].rl+t[k*2+1].ll;//同理
else return query(k*2+1,l);
}
}
int main(){
while(~scanf("%d%d",&n,&m)){
build(1,1,n);
top=0;
while(m--){
char str[10];
int x;
scanf("%s",str);
if(str[0]=='D'){
scanf("%d",&x);
updata(1,x,-1);
stk[top++]=x;//在stk中存下每次操作
}
else if(str[0]=='Q'){
scanf("%d",&x);
printf("%d\n",query(1,x));
}
else{
x=stk[--top];
updata(1,x,1);
}
}
}
return 0;
}


小结:线段树的大概框架都有个模板,基本就是建立个完全二叉树,在树上查找更新。区别在于,每一棵树上需要维护的值和方式,问题不同,维护的值,数量和方式也不同,这才是需要关注的地方。(刚接触时的小结)

(待续。。。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: