您的位置:首页 > 其它

初学线 c2f2 段树总结

2016-08-01 15:53 85 查看
线段树善于解决区间最值,和,合并等问题,运用很广泛,下面是一些总结:(自我感觉错误和不足之处有点多啊



单点更新模版:
#define MAXN 100005
struct node
{
int l,r,mi,mx,sum;//表示线段[l,r],中的最小、大值,及和值
}tr[MAXN*4];
int a[MAXN];
void build(int id,int l,int r)
{
tr[id].l=l;
tr[id].r=r;
if(l==r)//叶子节点
{
tr[id].mx=tr[id].mi=tr[id].sum=a[l];
}
else
{
int mid=(l+r)/2;
build(id*2,l,mid);//往左儿子建树
build(id*2+1,mid+1,r);//往右儿子建树
//对于编号为id的节点(线段),它左右儿子都建完了
//接下来就是更新它自身的值,即合并它左右儿子。
tr[id].mx=max(tr[id*2].mx,tr[id*2+1].mx);
tr[id].mi=min(tr[id*2].mi,tr[id*2+1].mi);
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
}
}
int quary(int id,int l,int r)//查询[l,r]区间的最小值、最大值、和值
{
if(l<=tr[id].l&&tr[id].r<=r)
//如果编号为id的节点(线段)被[l,r]区间覆盖了,则直接返回此线段的结果
{
//return tr[id].mi;
//return tr[id].mx;
return tr[id].sum;
}
else
{
int mid=(tr[id].l+tr[id].r)/2;
if(r<=mid)return quary(id*2,l,r);//r<=mid 说明我们要找的区间在左边
else if(l>mid)return quary(id*2+1,l,r);//l>mid 我们要找的区间在左边
else//之间
{
int x=quary(id*2,l,r);//先找左边
int y=quary(id*2+1,l,r);//再找右边
//最后在合并,返回
//return min(x,y);
// return max(x,y);
return x+y;
}
}
}
void update(int id,int pos,int val)//单点更新,把a[pos]修改成val
{
if(tr[id].l==tr[id].r)
{
tr[id].mi=tr[id].mx=tr[id].sum=val;
}
else
{
int mid=(tr[id].l+tr[id].r)/2;
if(pos<=mid)update(id*2,pos,val);
else update(id*2+1,pos,val);
//同样的道理,回溯的时候有两个儿子节点更新id
//tr[id].mx=max(tr[id*2].mx,tr[id*2+1].mx);
//tr[id].mi=min(tr[id*2].mi,tr[id*2+1].mi);
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
}
}
区间更新模版:
#define MAXN 100010
int a[MAXN];
struct node
{
int l,r,mi,mx;
long long int sum,sign;//long long 根据实际情况而定
}tr[MAXN*4];
void build(int id,int l,int r)
{
tr[id].l=l;
tr[id].r=r;
tr[id].sign=0;//区间更新初始化
if(l==r)
tr[id].sum=a[l];//tr.[id].mi=tr[id].mx=tr[id].sum=a[l]
else
{
int mid=(l+r)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
//tr[id].mx=max(tr[id*2].mx,tr[id*2+1].mx);
//tr[id].mi=min(tr[id*2].mi,tr[id*2+1].mi);
}
}
void add(int id,int l,int r,long long int val)
{
if(l==tr[id].l&&tr[id].r==r)
tr[id].sign+=val;//标记要加的值
else
{
//tr[id].ma+=val;
//tr[id].mi-=val;
tr[id].sum+=val*(r-l+1);//注意,这一步极其重要,这是找到你要标记区间的前面的大区间的维护,因为以后都是向下更新,所以上面的要提前更新
int mid=(tr[id].l+tr[id].r)/2;
if(r<=mid)
add(id*2,l,r,val);
else if (l>mid)
add(id*2+1,l,r,val);
else
{
add(id*2,l,mid,val);
add(id*2+1,mid+1,r,val);
}
}
}
long long int query(int id,int l,int r)//long long 视情况而定
{
if(l==tr[id].l&&tr[id].r==r)
return tr[id].sum+(r-l+1)*tr[id].sign;
//tr[id].ma+=tr[id].sign;
//tr[id].mi-=tr[id].sign;
else
{
tr[id].sum+=(tr[id].r-tr[id].l+1)*tr[id].sign;//对所到区间都跟新,不管有没有标记,其实也可以判断一下,在看看更新与否
int mid=(tr[id].l+tr[id].r)/2;
add(id*2,tr[id].l,mid,tr[id].sign);//向下更新sign标记,若要向下求,就要向下更新
add(id*2+1,mid+1,tr[id].r,tr[id].sign);
tr[id].sign=0;
if(r<=mid)
return query(id*2,l,r);
else if(l>mid)
return query(id*2+1,l,r);
else
return query(id*2,l,mid)+query(id*2+1,mid+1,r);
}
}
以上的这个区间更新版本,跟雨神讲的有点出入,即要一直更新到恰好要标记的地方,上面的大区间值都要更新,而他们是最后查询才更新,再回溯上去
注意这个区间更新版本:(加了lazy,直接在这里面判断是否要向下更新)因为此题查询查询整个区间
void add(int id,int l,int r,int val)
{
if(l==tr[id].l&&tr[id].r==r)
{
tr[id].lazy=1;
tr[id].sign=val;
tr[id].sum+=(r-l+1)*val;
}
else
{
int mid=(tr[id].l+tr[id].r)/2;
if(tr[id].lazy==1)
{
add(id*2,tr[id].l,mid,tr[id].sign);//即这里,上面模版是在query中再往下更新,其实都差不多
add(id*2+1,mid+1,tr[id].r,tr[id].sign);
tr[id].sign=0;
tr[id].lazy=0;
}
if(r<=mid)
add(id*2,l,r,val);
else if (l>mid)
add(id*2+1,l,r,val);
else
{
add(id*2,l,mid,val);
add(id*2+1,mid+1,r,val);
}
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线段树 区间合并