1.树状数组
例题1
程序设计
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std ;
const int N = 100010;
int n , m ; //数的个数 n ; 操作次数 m
int a[N],tr[N]; //原数组 a[N] , 树状数组 tr[N]
int lowbit(int x) //返回x的最后一位1
{
return x&-x;
}
void add(int x, int v) //在x位置加v ,并且相关联的数组也加v
{
for(int i=x; i<=n ; i += lowbit(i)) tr[i] += v ;
}
int query(int x) //查询x的前缀和
{
int res = 0 ;
for (int i=x ; i ; i -= lowbit(i)) res += tr[i];
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]); //读取原数组
for(int i=1;i<=n;i++) add(i,a[i]); //搭建树状数组
while(m--)
{
int k,x,y;
scanf("%d%d%d",&k,&x,&y);
if(k==0) printf("%d\n",query(y)-query(x-1)); //x到y所有数的和
else add(x,y);
}
return 0;
}
例题2
题目分析
题目要求某一个点(x,y)左下方星星的个数(不包括自己),且星星按y坐标增序给出,y 坐标相同的按x坐标增序给出, 因此对于每个新来的点(x,y), y是当前纵坐标的最大值,只需要求[x]中星星出现的数量即可。即为前缀和。
程序设计
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std ;
const int N = 32010 ;
int n ; //n个星星
int tr[N] , level[N]; //树:tr[N] 等级:level[N]
int lowbit(int x)
{
return x&-x ;
}
void add(int x)
{
for(int i=x ; i < N ; i += lowbit(i)) tr[i] ++ ;
}
int query(int x)
{
int res = 0;
for(int i=x ; i ; i -= lowbit(i)) res += tr[i] ;
return res ;
}
int main()
{
int x , y ;
scanf("%d",&n);
for(int i=1 ; i<=n ; i++)
{
scanf("%d%d",&x,&y) ;
x++ ; //树状数组从1开始 , 但坐标从0开始,让横坐标x++
level[query(x)] ++ ; //查询x的前缀和,更新等级为level[query(x)] 的加 1
add(x); //搭建树状数组
}
for(int i=0 ; i<n ; i++) printf("%d\n",level[i]) ;
return 0;
}
2.线段树
例1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100010;
int n,m; //数的个数n 操作次数m。
int w[N]; //记录一下权重
struct node{
int l,r; //左右区间
int sum; //总和
}tr[N*4]; //记得开 4 倍空间
void push_up(int u) //利用它的两个儿子来算一下它的当前节点信息
{
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum; //左儿子 u<<1 ,右儿子 u<<1|1
}
/**
* build :搭建线段树
* u :当前节点编号
* l :左边界
* r :右边界
* 无返回值
*/
void build(int u,int l,int r)
{
//如果当前已经是叶节点了,那我们就直接赋值就可以了
if(l==r) tr[u]={l,r,w[r]};
//否则的话,说明当前区间长度至少是 2 ,那么我们需要把当前区间分为左右两个区间,那先要找边界点
else
{
tr[u]={l,r};//这里记得赋值一下左右边界的初值
int mid=l+r>>1;//边界的话直接去计算一下 l + r 的下取整
build(u<<1,l,mid);//先递归一下左儿子
build(u<<1|1,mid+1,r);//然后递归一下右儿子
push_up(u);//做完两个儿子之后的话呢 push_up更新一下当前节点信息
}
}
/**
* query :查询
* u :查询节点编号
* l :左边界
* r :右边界
* return sum
*/
int query(int u,int l,int r) //查询的过程是从根结点开始往下找对应的一个区间
{
//如果当前区间已经完全被包含了,那么我们直接返回它的值就可以了
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
//否则的话我们需要去递归来算
int mid=tr[u].l+tr[u].r>>1; //计算一下当前区间的中点是多少
int sum=0;//用 sum 来表示一下我们的总和
//判断中点和左右两边有没有交集
if(mid>=l) sum+=query(u<<1,l,r); //看一下我们当前区间的中点和左边有没有交集
if(r>=mid+1) sum+=query(u<<1|1,l,r); //看一下我们当前区间的中点和右边有没有交集
return sum;
}
/**
* modify :修改
* u :当前节点的编号
* l :要修改的位置
* r :要修改的值
* 无返回值
*/
void modify(int u,int x,int v)
{
//如果当前已经是叶节点了,那我们就直接让他的总和加上 v 就可以了
if(tr[u].l==tr[u].r)tr[u].sum+=v;
//否则的话我们需要去递归来算
else
{
int mid=tr[u].l+tr[u].r>>1; //计算一下当前区间的中点是多少
//判断 x 是在左半边还是在右半边
if(x<=mid) modify(u<<1,x,v); //如果是在左半边,那就修改左儿子
else modify(u<<1|1,x,v); //如果在右半边,那就修改右儿子
push_up(u); //更新完之后刷新当前节点的信息
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
build(1,1,n); /*第一个参数是根节点的下标,根节点是一号点,然后初始区间是 1 到 n */
//后面的话就是一些修改操作了
while(m--)
{
int k,a,b;
scanf("%d%d%d",&k,&a,&b);
if(!k) printf("%d\n",query(1,a,b));//求和的时候,也是传三个参数,第一个的话是根节点的编号 ,第二个的话是我们查询的区间
//第一个参数也就是当前节点的编号
else
modify(1,a,b);//第一个参数是根节点的下标,第二个参数是要修改的位置,第三个参数是要修改的值
}
return 0;
}
例2
与上一道题类似,只要把求和过程改成求最大值即可。题目只要求查询,不需要再写修改函数
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m; //数的个数n 操作次数m。
int w[N]; //记录一下权重
struct node{
int l,r ; //该节点左右区间
int maxv ; //该节点的最大值
}tr[N*4];
/**
* build :搭建线段树
* u :当前节点编号
* l :左边界
* r :右边界
* 无返回值
*/
void build(int u, int l, int r)
{
if (l == r)tr[u] = { l,r,w[r] };
else
{
tr[u] = { l,r };
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv);
}
}
int query(int u,int l,int r)
{
if (tr[u].l >= l && tr[u].r <= r)return tr[u].maxv;
int mid = tr[u].l + tr[u].r >> 1;
int maxv = INT_MIN;
if (l <= mid)maxv = max(maxv, query(u << 1, l, r));
if (r >= mid + 1)maxv = max(maxv, query(u << 1 | 1, l, r));
return maxv;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> w[i];
build(1, 1, n);
int l, r;
while (m--)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(1,l,r));
}
return 0;
}