您的位置:首页 > 其它

线段树

2015-12-02 20:36 387 查看
线段树,是可以进行如下操作的树状数据结构。

1.对区间[l,r]中所有的数字+x/修改为x;

2.查询区间[l,r]的总和/最大值/最小值……

线段树的具体思想可以参考百度百科。

以下是几道线段树的例题。

codevs 1082(线段树练习3)

线段树裸题,直接套用模板即可,代码如下。

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
struct node
{
int l,r;
long long sum,lazy;
} tree[800050];
int n;
void push_down(int id)
{
tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;
tree[id*2].lazy+=tree[id].lazy;
tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;
tree[id*2+1].lazy+=tree[id].lazy;
tree[id].lazy=0;
}
void build_tree(int id,int l,int r)
{
tree[id].l=l,tree[id].r=r;
tree[id].lazy=0;
if(l==r)
{
scanf("%lld",&tree[id].sum);
return;
}
int mid=(l+r)/2;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;
}
void insert(int id,int l,int r,long long num)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
//   cout<<id<<"we"<<endl;
tree[id].sum+=num*(tr-tl+1);
tree[id].lazy+=num;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
insert(id*2,l,r,num);
if(mid<r)
insert(id*2+1,l,r,num);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
long long ret=0;
void query(int id,int l,int r)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
// cout<<id<<endl;
ret+=tree[id].sum;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
query(id*2,l,r);
if(mid<r)
query(id*2+1,l,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
int main()
{
int n;
cin>>n;
build_tree(1,1,n);
int q;
cin>>q;
while(q--)
{
int a;
scanf("%d",&a);
if(a==1)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
insert(1,x,y,(long long)z);
}
else
{
int x,y;
scanf("%d%d",&x,&y);
ret=0;
query(1,x,y);
cout<<ret<<endl;
}
}
}


HDU 1698

简单的以dota中屠夫为背景的题目。题意如下,给定1~n的初始均为1的数列,每次将l~r区间里的数字全部修改为一个给定值,求最终的区间总和。

利用线段树可以简单的求解,注意在区间修改时lazy标记的-1的问题。

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
struct node
{
int l,r;
long long sum,lazy;
} tree[800050];
int n;
void push_down(int id)
{
if(tree[id].lazy!=-1)
{
tree[id*2].sum=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;
tree[id*2].lazy=tree[id].lazy;
tree[id*2+1].sum=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;
tree[id*2+1].lazy=tree[id].lazy;
}
tree[id].lazy=-1;
}
void build_tree(int id,int l,int r)
{
tree[id].l=l,tree[id].r=r;
tree[id].lazy=0;
if(l==r)
{
tree[id].sum=1;
return;
}
int mid=(l+r)/2;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;
}
void insert(int id,int l,int r,long long num)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
tree[id].sum=num*(tr-tl+1);
tree[id].lazy=num;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
insert(id*2,l,r,num);
if(mid<r)
insert(id*2+1,l,r,num);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
long long ret=0;
void query(int id,int l,int r)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
// cout<<id<<endl;
ret+=tree[id].sum;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
query(id*2,l,r);
if(mid<r)
query(id*2+1,l,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
int T;
int main()
{
cin>>T;
for(int t=1;t<=T;t++)
{
int n;
cin>>n;
build_tree(1,1,n);
insert(1,1,n,1);
int q;
cin>>q;
while(q--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
insert(1,a,b,c);
}
ret=0;
query(1,1,n);
printf("Case %d: The total value of the hook is %I64d.\n",t,ret);
}
}


bzoj1012

具体题意请阅读链接中的题意,这道题对于审题能力有很高的要求。

简单题,运用线段树十分容易理解,也可以使用单调队列或单调栈求解,以下给出使用线段树求解的代码。

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
struct node
{
int l,r;
long long maxn,lazy;
} tree[800050];
int n;
void push_down(int id)
{
if(tree[id].lazy!=-1)
{
tree[id*2].maxn=tree[id].lazy;
tree[id*2].lazy=tree[id].lazy;
tree[id*2+1].maxn=tree[id].lazy;
tree[id*2+1].lazy=tree[id].lazy;
}
tree[id].lazy=-1;
}
void build_tree(int id,int l,int r)
{
tree[id].l=l,tree[id].r=r;
tree[id].lazy=-1;
tree[id].maxn=0;
if(l==r)
{
return;
}
int mid=(l+r)/2;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].maxn=max(tree[2*id].maxn,tree[2*id+1].maxn);
}
void insert(int id,int l,int r,long long num)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
//   cout<<id<<"we"<<endl;
tree[id].maxn=num;
tree[id].lazy=num;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
insert(id*2,l,r,num);
if(mid<r)
insert(id*2+1,l,r,num);
tree[id].maxn=max(tree[2*id].maxn,tree[2*id+1].maxn);
}
long long ret=0;
void query(int id,int l,int r)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
// cout<<id<<endl;
ret=max(tree[id].maxn,ret);
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
query(id*2,l,r);
if(mid<r)
query(id*2+1,l,r);
tree[id].maxn=max(tree[2*id].maxn,tree[2*id+1].maxn);
}
int main()
{
long long m,q;
cin>>q>>m;
int tot=1;
build_tree(1,1,200000);
while(q--)
{
char c[3];
long long num;
scanf("%s%lld",&c,&num);
if(c[0]=='A')
{
insert(1,tot,tot,(num+ret)%m);
tot++;
}
else
{
ret=0;
query(1,tot-num,tot-1);
printf("%lld\n",ret);
}
}
}


poj 2828

一道以春运为背景的题目,一共有n次插入,每次插入时将编号为val的人插在第pos个人身后,求最终val的序列。

从后往前枚举,只需插入至从第一个开始的第pos+1个空位即可,代码如下。

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
struct node
{
int l,r;
long long sum,lazy;
} tree[800050];
int n;
void push_down(int id)
{
tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;
tree[id*2].lazy+=tree[id].lazy;
tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;
tree[id*2+1].lazy+=tree[id].lazy;
tree[id].lazy=0;
}
void build_tree(int id,int l,int r)
{
tree[id].l=l,tree[id].r=r;
tree[id].lazy=0;
if(l==r)
{
tree[id].sum=1;
return;
}
int mid=(l+r)/2;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;
}
void insert(int id,int l,int r,long long num)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
//   cout<<id<<"we"<<endl;
tree[id].sum+=num*(tr-tl+1);
tree[id].lazy+=num;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
insert(id*2,l,r,num);
if(mid<r)
insert(id*2+1,l,r,num);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
int ret=0;
void query(int id,int l,int r)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
if(tree[id].sum==0)
{
ret=min(ret,tl);
return;
}
if(tree[id].sum==tr-tl+1)
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
query(id*2,l,r);
if(mid<r)
query(id*2+1,l,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
int a[200050],b[200050],ans[200050];
void push(int id,int yu,int num)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl==tr)
{
tree[id].sum--;
ans[tl]=num;
return;
}
int lson=id*2,rson=id*2+1;
if(tree[lson].sum>=yu)
push(lson,yu,num);
else
push(rson,yu-tree[lson].sum,num);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
int main()
{
while(cin>>n)
{
build_tree(1,1,n);
for(int i=n;i>=1;i--)
{
scanf("%d%d",&a[i],&b[i]);
}
for(int i=1;i<=n;i++)
{
a[i]++;
push(1,a[i],b[i]);
}
for(int i=1;i<=n;i++)
printf("%d%c",ans[i],i==n?'\n':' ');
}
}


SGU 128

一道较难的题,具体题意请自主翻译。

解决时需要用到并查集,注意:最终所有的点必须全部联通。

代码如下:

#include<iostream>
#include<stdio.h>
#include<queue>
#include<utility>
using namespace std;
struct node
{
int l,r;
int sum,lazy;
} tree[80005];
int n;
int bcj[10005];
void ji()
{
for(int i=1;i<=n;i++)
bcj[i]=i;
}
int cha(int x)
{
if(bcj[x]==x)
return x;
return bcj[x]=cha(bcj[x]);
}
void bing(int x,int y)
{
bcj[cha(x)]=cha(y);
}
priority_queue<pair<int,int> > q[20005];int q1[20005];
void push_down(int id)
{
tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;
tree[id*2].lazy+=tree[id].lazy;
tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;
tree[id*2+1].lazy+=tree[id].lazy;
tree[id].lazy=0;
}
void build_tree(int id,int l,int r)
{
tree[id].l=l,tree[id].r=r;
tree[id].lazy=0;
if(l==r)
{
return;
}
int mid=(l+r)/2;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;
}
void insert(int id,int l,int r,long long num)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
//   cout<<id<<"we"<<endl;
tree[id].sum+=num*(tr-tl+1);
tree[id].lazy+=num;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
insert(id*2,l,r,num);
if(mid<r)
insert(id*2+1,l,r,num);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
int yy[20050];
long long ret=0;
void query(int id,int l,int r)
{
int tl=tree[id].l,tr=tree[id].r;
if(tl>=l&&tr<=r)
{
// cout<<id<<endl;
ret+=tree[id].sum;
return;
}
push_down(id);
int mid=(tl+tr)/2;
if(mid>=l)
query(id*2,l,r);
if(mid<r)
query(id*2+1,l,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}int main()
{
cin>>n;
ji();
int maxx=0,maxy=0;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
x+=10001,y+=10001;
q[y].push(make_pair(x,i));
q1[x]++;
maxx=max(x,maxx);
maxy=max(y,maxy);
}
build_tree(1,1,maxx);
long long ans=0;
int tot=0;
for(int i=1;i<=maxy;i++)
{
int qs=q[i].size();
if(qs)
++tot;
if(qs%2==1)
{
cout<<0<<endl;
return 0;
}
pair<int,int> pre;
for(int j=1;j<=qs;j++)
{
pair<int,int> e=q[i].top();
int ef=e.first,es=e.second,pref=pre.first,pres=pre.second;
q[i].pop();
if(j%2==0)
{
//cout<<es<<' '<<pres<<endl;
bing(es,pres);
ans-=ef;
ret=0;
int flag1=0,flag2=0;
query(1,ef,ef);
bing(es,pres);
if(ret)
{
ans+=i-ret;
bing(yy[ef],es);
flag1=1;
insert(1,ef,ef,-ret);
}
ret=0;
query(1,pref,pref);
if(ret)
{
ans+=i-ret;
bing(yy[pref],pres);
flag2=1;
insert(1,pref,pref,-ret);
}
ret=0;
query(1,ef,pref);
if(ret)
{
cout<<0<<endl;
return 0;
}
else
{
if(!flag1) insert(1,ef,ef,i);
yy[ef]=es;
if(!flag2) insert(1,pref,pref,i);
yy[pref]=pres;
}
}
else {ans+=ef,pre=e;}
// cout<<ans<<endl;
}
}
int tot1=0;
for(int i=1;i<=maxx;i++)
{
if(q1[i]) ++tot1;
if(q1[i]%2==1)
{
cout<<0<<endl;
return 0;
}
}
int flag=0;
for(int i=1;i<n;i++)
if(cha(i)!=cha(i+1))
flag=1;
if(flag==1)
cout<<0<<endl;
else
cout<<ans<<endl;
}


鉴于并查集写的太过形象生动,请读者借鉴时注意。

对于想要继续学习线段树的相关知识,可以继续学习以下算法/数据结构:

树状数组(BIT)(作用类似于线段树,但只支持单点的修改)

zkw线段树(线段树的非递归写法)

主席树(可持久化线段树)

树链剖分(线段树在一棵树上的应用)

对于更多的线段树资料,可以参考《挑战程序设计竞赛(第二版)》(【秋叶拓哉 岩田阳一 北川宜稔 著,巫泽俊 庄俊元 李津羽 译,人民邮电出版社2013年出版)3.3节以及《算法竞赛入门经典——训练指南》(刘汝佳 著,清华大学出版社2012年出版)3.2.3和3.2.4节

另外,codeforces上也有许多线段树的好题,以下题目可供训练参考:

251D

115E

384E

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