线段树详解C++版.docx

上传人:b****6 文档编号:8451503 上传时间:2023-01-31 格式:DOCX 页数:78 大小:76.85KB
下载 相关 举报
线段树详解C++版.docx_第1页
第1页 / 共78页
线段树详解C++版.docx_第2页
第2页 / 共78页
线段树详解C++版.docx_第3页
第3页 / 共78页
线段树详解C++版.docx_第4页
第4页 / 共78页
线段树详解C++版.docx_第5页
第5页 / 共78页
点击查看更多>>
下载资源
资源描述

线段树详解C++版.docx

《线段树详解C++版.docx》由会员分享,可在线阅读,更多相关《线段树详解C++版.docx(78页珍藏版)》请在冰豆网上搜索。

线段树详解C++版.docx

线段树详解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)

{

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 外语学习 > 英语考试

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1