您的位置:首页 > 其它

HDU 1754 线段树 及其 入门知识

2012-09-15 20:38 323 查看
刚开始以为看到这道题是提交,ac的人很多,以为是水题,用最简单的方法操作,超时!

线段树!!



线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

实际应用:(源自百度百科)

最简单的应用就是记录线段有否被覆盖,并随时查询当前被覆盖线段的总长度。那么此时可以在结点结构中加入一个变量int count;代表当前结点代表的子树中被覆盖的线段长度和。这样就要在插入(删除)当中维护这个count值,于是当前的覆盖总值就是根节点的count值了。

  另外也可以将count换成bool cover;支持查找一个结点或线段是否被覆盖。[1]

  实际上,通过在结点上记录不同的数据,线段树还可以完成很多不同的任务。例如,如果每次插入操作是在一条线段上每个位置均加k,而查询操作是计算一条线段上的总和,那么在结点上需要记录的值为sum。

  这里会遇到一个问题:为了使所有sum值都保持正确,每一次插入操作可能要更新O(N)个sum值,从而使时间复杂度退化为O(N)。

  解决方案是Lazy思想:对整个点进行的操作,先在结点上做标记,而并非真正执行,直到根据查询操作的需要分成两部分。

  根据Lazy思想,我们可以在不代表原线段的结点上增加一个值toadd,即为对这个结点,留待以后执行的插入操作k值的总和。对整个结点插入时,只更新sum和toadd值而不向下进行,这样时间复杂度可证明为O(logN)。

  对一个toadd值为0的结点整个进行查询时,直接返回存储在其中的sum值;而若对toadd不为0的一部分进行查询,则要更新其左右子结点的sum值,然后把toadd值传递下去,再对这个查询本身,左右子结点分别递归下去。时间复杂度也是O(logN)。

#include <iostream>
using namespace std;
struct Node
{
int l,r,mid,max;
}node[600000];

void init(int a,int b,int n)
{
node
.l=a;
node
.max=-1;
node
.r=b;
node
.mid=(a+b)/2;

if(a+1==b)
return;
init(a,(a+b)/2,n*2);
init((a+b)/2,b,2*n+1);
}

void update(int pos,int value,int n)
{
if(value>node
.max)
node
.max=value;
if((node
.l+1)==node
.r)
return;
if(pos<node
.mid)
{
update(pos,value,2*n);
}
if(pos>=node
.mid)
{
update(pos,value,2*n+1);
}
}

int query(int a,int b,int n)
{
if(node
.l==a && node
.r==b)
return node
.max;
if(a<node
.mid)
{
if(b<=node
.mid)
{
return query(a,b,2*n);
}
else
{
return max(query(a,node
.mid,2*n),query(node
.mid,b,2*n+1));
}
}
else
{
return query(a,b,2*n+1);
}
}

int main()
{
int n,m,t,a,b,i;
char ch;
while(cin >>n>>m)
{
init(1,n+1,1);
for(i=1;i<=n;i++)
{
scanf("%d",&t);
update(i,t,1);
}
for(i=1;i<=m;i++)
{
getchar();
cin >>ch;
cin >>a>>b;
if(ch=='Q')
cout <<query(a,b+1,1)<<endl;//注意是b+1而不是b。因为线段树是一个左开右闭的的区间。
else
update(a,b,1);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: