线段树详解C++版.docx
《线段树详解C++版.docx》由会员分享,可在线阅读,更多相关《线段树详解C++版.docx(78页珍藏版)》请在冰豆网上搜索。
线段树详解C++版
线段树的定义及特征
定义1:
线段树
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
一棵二叉树,记为T(a,b),参数a,b表示该节点表示区间[a,b]。
区间的长度b-a记为L。
递归定义T[a,b]:
若L>0:
[a,(a+b)div2]为T的左儿子
[(a+b)div2+1,b]为T的右儿子。
若L=0:
T为一个叶子节点。
表示区间[1,5]的线段树表示如下:
线段树支持的操作有:
构造,插入,查找,更新,删除;这些操作不一定都有,在实现上也不一定都是一个套路,这都取决于实际问题;实际上,通过在线段树节点上记录不同的信息,线段树可以完成很多不同的任务;线段树的高度为logn,这也就使得线段树可以在O(lgn)的时间完成插入、查询、删除等操作。
1、忠诚(TYVJ1038)
Description
老管家是一个聪明能干的人。
他为财主工作了整整10年,财主为了让自已账目更加清楚。
要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。
但是由于一些人的挑拨,财主还是对管家产生了怀疑。
于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:
在a到b号账中最少的一笔是多少?
为了让管家没时间作假他总是一次问多个问题。
Input
输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。
第二行为m个数,分别是账目的钱数后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。
Output
输出文件中为每个问题的答案。
具体查看样例。
SampleInput
103
12345678910
27
39
110
SampleOutput
231
参考程序:
#include
#include
#include
usingnamespacestd;
intn,m,i,k,x,y,num,a[100000],f[100000];
structss
{
intl,r,ls,rs,f,data;
}t[400001];
voidbuild(intl,intr)
{
inth;
num++;
h=num;
t[h].l=l;
t[h].r=r;
if(l!
=r)
{
t[h].ls=num+1;
t[num+1].f=h;
build(l,(l+r)/2);
t[h].rs=num+1;
t[num+1].f=h;
build(((l+r)/2)+1,r);
t[h].data=min(t[t[h].ls].data,t[t[h].rs].data);
}
else
{
t[h].data=a[l];f[l]=h;
}
}
intfind(inth,intp,intq)
{
intv;
if((t[h].l==p)&&(t[h].r==q))return(t[h].data);
v=(t[h].l+t[h].r)/2;
if(q<=v)return(find(t[h].ls,p,q));
if(p>v)return(find(t[h].rs,p,q));
return(min(find(t[h].ls,p,v),find(t[h].rs,v+1,q)));
}
intmain()
{
num=0;
t[1].f=0;
scanf("%d%d",&m,&n);
for(i=1;i<=m;i++)scanf("%d",&a[i]);
build(1,m);
for(i=1;i<=n-1;i++)
{
scanf("%d%d",&x,&y);
printf("%d",find(1,x,y));
}
scanf("%d%d",&x,&y);
printf("%d",find(1,x,y));
return0;
}
2、忠诚2(TYVJ1039)
Description
老管家是一个聪明能干的人。
他为财主工作了整整10年,财主为了让自已账目更加清楚。
要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。
但是由于一些人的挑拨,财主还是对管家产生了怀疑。
于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:
在a到b号账中最少的一笔是多少?
为了让管家没时间作假他总是一次问多个问题。
在询问过程中账本的内容可能会被修改
Input
输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。
接下来每行为3个数字,第一个p为数字1或数字2,第二个数为x,第三个数为y
当p=1则查询x,y区间
当p=2则改变第x个数为y
Output
输出文件中为每个问题的答案。
具体查看样例。
SampleInput
103
12345678910
127
220
1110
SampleOutput
20
题解一:
#include
#include
#include
usingnamespacestd;
intn,m,i,k,x,y,num,a[100000],f[100000];
structss
{
intl,r,ls,rs,f,data;
}t[400001];
voidbuild(intl,intr)
{
inth;
num++;
h=num;
t[h].l=l;
t[h].r=r;
if(l!
=r)
{
t[h].ls=num+1;
t[num+1].f=h;
build(l,(l+r)/2);
t[h].rs=num+1;
t[num+1].f=h;
build(((l+r)/2)+1,r);
t[h].data=min(t[t[h].ls].data,t[t[h].rs].data);
}
else
{
t[h].data=a[l];f[l]=h;
}
}
voidrebuild(intx)
{
if(x==0)return;
if(t[x].data!
=min(t[t[x].ls].data,t[t[x].rs].data))
{
t[x].data=min(t[t[x].ls].data,t[t[x].rs].data);
rebuild(t[x].f);
}
}
voidchange(intm,intx)
{
t[f[m]].data=x;
rebuild(t[f[m]].f);
}
intfind(inth,intp,intq)
{
intv;
if((t[h].l==p)&&(t[h].r==q))return(t[h].data);
v=(t[h].l+t[h].r)/2;
if(q<=v)return(find(t[h].ls,p,q));
if(p>v)return(find(t[h].rs,p,q));
return(min(find(t[h].ls,p,v),find(t[h].rs,v+1,q)));
}
intmain()
{
intflag=1;
num=0;
t[1].f=0;
scanf("%d%d",&m,&n);
for(i=1;i<=m;i++)scanf("%d",&a[i]);
build(1,m);
for(i=1;i<=n;i++)
{
scanf("%d",&k);
if(k==1)
{
if(flag==1)
{
scanf("%d%d",&x,&y);
printf("%d",find(1,x,y));
flag=0;
}
else
{
scanf("%d%d",&x,&y);
printf("%d",find(1,x,y));
}
}
if(k==2)
{
scanf("%d%d",&x,&y);
change(x,y);
}
}
return0;
}
3、线段树练习
题目描述:
一行N个方格,开始每个格子里都有一个整数。
现在动态地提出一些问题和修改:
提问的形式是求某一个特定的子区间[a,b]中所有元素的和;修改的规则是指定某一个格子x,加上或者减去一个特定的值A。
现在要求你能对每个提问作出正确的回答。
1≤N<100000,,提问和修改的总数m<10000条。
输入描述InputDescription
输入文件第一行为一个整数N,接下来是n行n个整数,表示格子中原来的整数。
接下一个正整数m,再接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。
输出描述OutputDescription
共m行,每个整数
样例输入SampleInput
6
4
5
6
2
1
3
4
135
214
119
226
样例输出SampleOutput
22
22
数据范围及提示DataSize&Hint
1≤N≤100000,m≤10000。
参考程序:
#include
#include
usingnamespacestd;
structtree{
intl;
intr;
intls;
intrs;
intsum;
}t[500000];
intn,m,i,k,x,y,num,a[100000],f[100000];
boolflag;
voidbuild(intp1,intl,intr)
{
intm,ii;
t[p1].l=l;
t[p1].r=r;
m=(t[p1].l+t[p1].r)/2;
for(ii=l;ii<=r;ii++)t[p1].sum+=a[ii];
if(l==r)return;
build(2*p1,l,m);
build(2*p1+1,m+1,r);
}
voidmodify(intp,intx1,inty1)
{
intm;
if((t[p].l<=x1)&&(t[p].r>=x1))t[p].sum+=y1;
if(t[p].l==t[p].r)return;
m=(t[p].l+t[p].r)/2;
if(m>=x1)modify(2*p,x1,y1);
elsemodify(2*p+1,x1,y1);
}
intfind(inth,intp,intq)
{
intm;
if((t[h].l==p)&&(t[h].r==q))return(t[h].sum);
m=(t[h].l+t[h].r)/2;
if(q<=m)return(find(2*h,p,q));
elseif(p>=m+1)return(find(2*h+1,p,q));
elsereturn(find(2*h,p,m)+find(2*h+1,m+1,q));
}
intmain()
{
scanf("%d",&m);
for(i=1;i<=m;i++)
{
scanf("%d",&a[i]);
}
build(1,1,m);
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&k);
if(k==2)
{
scanf("%d%d",&x,&y);
printf("%d\n",find(1,x,y));
}
if(k==1)
{
scanf("%d%d",&x,&y);
modify(1,x,y);
}
}
return0;
}
4、线段树练习2
题目描述Description
给你N个数,有两种操作
1:
给区间[a,b]的所有数都增加X
2:
询问第i个数是什么?
输入描述InputDescription
第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,表示操作的个数.接下来Q行每行若干个整数。
如果第一个数是1,后接3个正整数a,b,X,表示在区间[a,b]内每个数增加X,如果是2,后面跟1个整数i,表示询问第i个位置的数是多少。
输出描述OutputDescription
对于每个询问输出一行一个答案
样例输入SampleInput
3
1
2
3
2
1232
23
样例输出SampleOutput
5
数据范围及提示DataSize&Hint
数据范围
1<=n<=100000
1<=q<=100000
参考程序:
#include
#include
usingnamespacestd;
intn,q,a[100001];
structdata{
intl,r,x;
}tr[400001];
voidbuild(intk,ints,intt)
{
tr[k].l=s;tr[k].r=t;
if(s==t){tr[k].x=a[s];return;}
intmid=(s+t)>>1;
build(k<<1,s,mid);
build(k<<1|1,mid+1,t);
}
voidupdate(intk,inta,intb,intx)
{
intl=tr[k].l,r=tr[k].r;
if(l==a&&b==r){tr[k].x+=x;return;}
intmid=(l+r)>>1;
if(b<=mid)update(k<<1,a,b,x);
elseif(a>mid)update(k<<1|1,a,b,x);
else
{
update(k<<1,a,mid,x);
update(k<<1|1,mid+1,b,x);
}
}
intask(intk,intx)
{
intl=tr[k].l,r=tr[k].r;
if(l==r){returntr[k].x;}
intmid=(l+r)>>1;
if(x<=mid)returntr[k].x+ask(k<<1,x);
elsereturntr[k].x+ask(k<<1|1,x);
}
intmain()
{
scanf("%d",&n);
for(inti=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&q);
for(inti=1;i<=q;i++)
{
intt,a,b,x;
scanf("%d",&t);
if(t==1){
scanf("%d%d%d",&a,&b,&x);
update(1,a,b,x);
}
else{
scanf("%d",&x);
printf("%d\n",ask(1,x));
}
}
return0;
}
4.1线段树练习3
题目描述Description
给你N个数,有两种操作:
1:
给区间[a,b]的所有数增加X
2:
询问区间[a,b]的数的和。
输入描述InputDescription
第一行一个正整数n,接下来n行n个整数,
再接下来一个正整数Q,每行表示操作的个数,
如果第一个数是1,后接3个正整数,
表示在区间[a,b]内每个数增加X,如果是2,
表示操作2询问区间[a,b]的和是多少。
pascal选手请不要使用readln读入
输出描述OutputDescription
对于每个询问输出一行一个答案
样例输入SampleInput
3
1
2
3
2
1232
223
样例输出SampleOutput
9
数据范围及提示DataSize&Hint
数据范围
1<=n<=200000
1<=q<=200000
参考程序:
#include
#include
usingnamespacestd;
intn,q,a[200001];
structdata{
intl,r;
longlongsum;
inttag;
}tr[800001];
voidbuild(intk,ints,intt)
{
tr[k].l=s;tr[k].r=t;
if(s==t){tr[k].sum=a[s];return;}
intmid=(s+t)>>1;
build(k<<1,s,mid);
build(k<<1|1,mid+1,t);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
voidpushdown(intk)
{
intx=tr[k].r-tr[k].l+1;
tr[k<<1].tag+=tr[k].tag;
tr[k<<1|1].tag+=tr[k].tag;
tr[k<<1].sum+=(x-(x>>1))*tr[k].tag;
tr[k<<1|1].sum+=(x>>1)*tr[k].tag;
tr[k].tag=0;
}
voidupdate(intk,inta,intb,intx)
{
intl=tr[k].l,r=tr[k].r;
if(a==l&&r==b)
{
tr[k].tag+=x;
tr[k].sum+=(b-a+1)*x;
return;
}
if(tr[k].tag)pushdown(k);
intmid=(l+r)>>1;
if(b<=mid)update(k<<1,a,b,x);
elseif(a>mid)update(k<<1|1,a,b,x);
else
{
update(k<<1,a,mid,x);
update(k<<1|1,mid+1,b,x);
}
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
longlongask(intk,inta,intb)
{
intl=tr[k].l,r=tr[k].r;
if(a==l&&b==r){returntr[k].sum;}
if(tr[k].tag)pushdown(k);
intmid=(l+r)>>1;
if(b<=mid)returnask(k<<1,a,b);
elseif(a>mid)returnask(k<<1|1,a,b);
elsereturn(ask(k<<1,a,mid)+ask(k<<1|1,mid+1,b));
}
intmain()
{
scanf("%d",&n);
for(inti=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&q);
for(inti=1;i<=q;i++)
{
intt,a,b,x;
scanf("%d",&t);
if(t==1){
scanf("%d%d%d",&a,&b,&x);
update(1,a,b,x);
}
else{
scanf("%d%d",&a,&b);
printf("%lld\n",ask(1,a,b));
}
}
return0;
}
5、简单题(easy)
有一个n个元素的数组,每个元素初始均为0。
有m条指令,要么让其中一段连续序列数字反转——0变1,1变0(操作1),要么询问某个元素的值(操作2)。
例如当n=20时,10条指令如下:
操作
回答
操作后的数组
1110
N/A
11111111110000000000
26
1
11111111110000000000
212
0
11111111110000000000
1512
N/A
11110000001100000000
26
0
11110000001100000000
215
0
11110000001100000000
1616
N/A
11110111110011110000
11117
N/A
111101*********01000
212
1
11110111111100001000
26
1
11110111111100001000
【输入文件】
输入文件easy.in第一行包含两个整数n,m,表示数组的长度和指令的条数,以下m行,每行的第一个数t表示操作的种类。
若t=1,则接下来有两个数L,R(L<=R),表示区间[L,R]的每个数均反转;若t=2,则接下来只有一个数I,表示询问的下标。
【输出文件】
每个操作2输出一行(非0即1),表示每次操作2的回答。
【样例】
easy.in
easy.out
2010
1110
26
212
1512
26
215
1616
11117
212
26
1
0
0
0
1
1
【限制】
50%的数据满足:
1<=n<=1,000,1<=m<=10,000
100%的数据满足:
1<=n<=100,000,1<=m<=500,000
这道题标准算法是用线段树来实现
#include
#include
#include
#include
#include
#include
#include
usingnamespacestd;
constintmaxn=100005;
structnode{
inta,b;
boollazy;
}Tree[maxn*4];
intN,M;
voidyin(int&x)
{