对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数, 时间复杂度 为O(logN)。而未优化的 空间复杂度 为2N,因此有时需要 离散化 让空间压缩。 ”————————————wikipedia#define lchild rt << 1, l, m
#define rchild rt << 1|1, m + 1, r
void build(int rt= 1,int l =1, int r= N)
{
if (l== r)
{
cin >> tree[rt]; //当然也可以用scanf等等
return;
}
int m =(l + r)>> 1;
build(lchild); build(rchild);
push_up(rt); //数据向上更新
}
/* 对于区间求和 */
void push_up(int rt){ tree[rt]= tree[rt<< 1]+ tree[rt<< 1| 1];}
/* 对于区间求最大值 */
void push_up(int rt){ tree[rt]= max(tree[rt<< 1], tree[rt<< 1| 1]); }
void update(int p, int delta,int rt=1,int l=1,int r= N)
{
if
(l == r)
{
tree[rt]+= delta;
return;
}
int m =(l + r)>> 1;
if (p<= m)
update(p, delta, lchild);
else update(p, delta, rchild);
push_up(rt);
}
void update(int L,int R, int delta,int rt =1, int l= 1,int r = N)
{
if (L<= l && r<= R)
{
tree[rt]+= delta* (r- l +1);
lazy[rt]+= delta;
return;
}
if (lazy[rt])
push_down(rt, r - l + 1); //更新惰性标记
int m =(l + r)>> 1;
if (L<= m) update(L, R, delta, lchild);
if (R> m) update(L, R, delta, rchild);
push_up(rt);
}
void push_down(int rt,int len)
{
tree[rt << 1]+= lazy[rt]* (len- (len>> 1));
lazy[rt << 1]+= lazy[rt];
tree[rt << 1 | 1]+= lazy[rt]* (len>> 1);
lazy[rt << 1 | 1]+= lazy[rt];
lazy[rt]= 0;
}
void push_down(int rt)
{
tree[rt << 1]+= lazy[rt];
lazy[rt << 1]+= lazy[rt];
tree[rt << 1 | 1]+= lazy[rt];
lazy[rt << 1 | 1]+= lazy[rt];
lazy[rt]= 0;
}
int query(int L,int R, int rt= 1,int l =1, int r= N)
{
if (L<= l && r<= R)return tree[rt];
if (lazy[rt]) push_down(rt, r - l +1);
int m =(l + r)>> 1, ret= 0;
if (L<= m) ret+= query(L, R, lchild);
if (R> m) ret+= query(L, R, rchild);
return ret;
}
一行N个方格,开始每个格子里都有一个整数。现在动态地提出一些问题和修改:提问的形式是求某一个特定的子区间[a,b]中所有元素的和;修改的规则是指定某一个格子x,加上或者减去一个特定的值A。现在要求你能对每个提问作出正确的回答。1≤N<100000,,提问和修改的总数m<10000条。
输入文件第一行为一个整数N,接下来是n行n个整数,表示格子中原来的整数。接下一个正整数m,再接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。
共m行,每个整数
6
4
5
6
2
1
3
4
1 3 5
2 1 4
1 1 9
2 2 6
22
22
#include
#include
#include
#include
#include
#define lchild rt<<1,l,m
#define rchild rt<<1|1,m+1,r
using namespace std;
int n,m;
int a,b,c;
int tree[400001];
int arr[100001];
void push_up(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void build(int rt=1,int l=1,int r=n)
{
if (l==r)
{
tree[rt]=arr[l];
return;
}
int m=(l+r)>>1;
build(lchild);build(rchild);
push_up(rt);
}
void update(int p,int delta,int rt=1,int l=1,int r=n)
{
if (l==r)
{
tree[rt]+=delta;
return;
}
int m=(l+r)>>1;
if (p<=m) update(p,delta,lchild);
else update(p,delta,rchild);
push_up(rt);
}
int query(int L,int R,int rt=1,int l=1,int r=n)
{
if (L<=l&&r<=R) return tree[rt];
int m=(l+r)>>1, ret=0;
if (L<=m) ret+=query(L,R,lchild);
if (R>m) ret+=query(L,R,rchild);
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",arr+i);
build(1,1,n);
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
switch(a)
{
case 1:
update(b,c);
break;
case 2:
cout<
给你N个数,有两种操作
1:给区间[a,b]的所有数都增加X
2:询问第i个数是什么?
第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,表示操作的个数. 接下来Q行每行若干个整数。如果第一个数是1,后接3个正整数a,b,X,表示在区间[a,b]内每个数增加X,如果是2,后面跟1个整数i, 表示询问第i个位置的数是多少。
对于每个询问输出一行一个答案
3
1
2
3
2
1 2 3 2
2 3
5
数据范围
1<=n<=100000
#include
#include
#include
#include
#include
#define lchild rt<<1,l,m
#define rchild rt<<1|1,m+1,r
using namespace std;
int n,q,tree[210000],lazy[210000];
int a,b,c,d;
void push_up(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void push_down(int rt, int len)
{
tree[rt<<1]+=lazy[rt]*(len-(len>>1));
lazy[rt<<1]+=lazy[rt];
tree[rt<<1|1]+=lazy[rt]*(len>>1);
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
void build(int rt=1,int l=1,int r=n)
{
if (l==r)
{
scanf("%d",&tree[rt]);
return;
}
int m=(l+r)>>1;
build(lchild);
build(rchild);
push_up(rt);
}
void update(int L,int R,int delta,int rt=1,int l=1,int r=n)
{
if (L<=l&&r<=R)
{
tree[rt]+=delta*(r-l+1);
lazy[rt]+=delta;
return;
}
if (lazy[rt]) push_down(rt,r-l+1);
int m=(l+r)>>1;
if (L<=m) update(L,R,delta,lchild);
if (R>m) update(L,R,delta,rchild);
push_up(rt);
}
int query(int L,int R,int rt=1,int l=1,int r=n)
{
if (L<=l&&r<=R) return tree[rt];
if (lazy[rt]) push_down(rt,r-l+1);
int m=(l+r)>>1,ret=0;
if (L<=m) ret+=query(L,R,lchild);
if (R>m) ret+=query(L,R,rchild);
return ret;
}
int main()
{
scanf("%d",&n);
build();
scanf("%d",&q);
for (int i=1;i<=q;i++)
{
scanf("%d",&a);
switch (a)
{
case 1:
scanf("%d%d%d",&b,&c,&d);
update(b,c,d);
break;
case 2:
scanf("%d",&b);
cout<
给你N个数,有两种操作:
1:给区间[a,b]的所有数增加X
2:询问区间[a,b]的数的和。
第一行一个正整数n,接下来n行n个整数,
再接下来一个正整数Q,每行表示操作的个数,
如果第一个数是1,后接3个正整数,
表示在区间[a,b]内每个数增加X,如果是2,
表示操作2询问区间[a,b]的和是多少。
对于每个询问输出一行一个答案
3
1
2
3
2
1 2 3 2
2 2 3
9
数据范围
1<=n<=200000
1<=q<=200000
区间操作,区间查询(其实本来和第二题一样,但是数据范围巨大,将所有函数都改为longlong之后就没事了)
#include
#include
#include
#include
#include
#define lchild rt<<1,l,m
#define rchild rt<<1|1,m+1,r
using namespace std;
long long n,q,tree[810000],lazy[810000];
long long a,b,c,d;
void push_up(long long rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void push_down(long long rt, long long len)
{
tree[rt<<1]+=lazy[rt]*(len-(len>>1));
lazy[rt<<1]+=lazy[rt];
tree[rt<<1|1]+=lazy[rt]*(len>>1);
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
void build(long long rt=1,long long l=1,long long r=n)
{
if (l==r)
{
scanf("%lld",&tree[rt]);
return;
}
int m=(l+r)>>1;
build(lchild);
build(rchild);
push_up(rt);
}
void update(long long L,long long R,long long delta,long long rt=1,long long l=1,long long r=n)
{
if (L<=l&&r<=R)
{
tree[rt]+=delta*(r-l+1);
lazy[rt]+=delta;
return;
}
if (lazy[rt]) push_down(rt,r-l+1);
int m=(l+r)>>1;
if (L<=m) update(L,R,delta,lchild);
if (R>m) update(L,R,delta,rchild);
push_up(rt);
}
long long query(long long L,long long R,long long rt=1,long long l=1,long long r=n)
{
if (L<=l&&r<=R) return tree[rt];
if (lazy[rt]) push_down(rt,r-l+1);
int m=(l+r)>>1;
long long ret=0;
if (L<=m) ret+=query(L,R,lchild);
if (R>m) ret+=query(L,R,rchild);
return ret;
}
int main()
{
scanf("%lld",&n);
build();
scanf("%lld",&q);
for (int i=1;i<=q;i++)
{
scanf("%lld",&a);
switch (a)
{
case 1:
scanf("%lld%lld%lld",&b,&c,&d);
update(b,c,d);
break;
case 2:
scanf("%lld%lld",&b,&c);
printf("%lld\n",query(b,c));
break;
}
}
}
小结:作为一种中级数据结构,线段树在很多方面都有应用,在OI竞赛中,大部分时候线段树并不是单独出现也不止区间求和和区间最值两种,而是综合了多种问题或者进行了改进和加强,如 Codevs1217 借教室 (2012年NOIP全国联赛提高组Day2T2)Codevs19461946 阿狸的打字机(2011年NOI全国竞赛)等等
为了解决这些题目,首先就需要线段树的熟练掌握。 今天的题目只是一些裸线段树的基础中的基础,巩固线段树这种数据结构还需要在今后的练习中反复强化