树状数组和线段树
数组数组:
1.用来做单点修改
2.动态求区间和
线段树:
1.树状数组能干的都能干
2.还可以求区间最大值
3.求区间长度
4.有很广的应用
1.动态求连续区间和
题意:
给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b]的连续和。接下来 m行,每行包含三个整数 k,a,b(k=0,表示求子数列[a,b][a,b]的和;k=1表示第 a 个数加 b)。
数列从 11 开始计数。
数据范围
1≤n≤100000
1≤m≤100000
1≤a≤b≤n
思路:
这道题有俩种操作,1.给数组中的某个数增加一个数 2.求区间和。 所以这是一道动态求区间和的题目。虽然用前缀和也能做,但是本题数据量大,会超时。所以本题用树状数组和线段树来做。
树状数组的时间复杂度为logn,本题的数据复杂读就是nlogn是可以的。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100010;
int n,m;
int a[N],tr[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
tr[i]+=y;
}
int query(int a)
{
int sum=0;
for(int i=a;i;i-=lowbit(i)) sum+=tr[i];
return sum;
}
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==1)
{
add(x,y);
}else
{
cout<<query(y)-query(x-1)<<endl;
}
}
return 0;
}
本题还可以用线段树来做,线段树的原理图:
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100010;
int w[N];
struct Node{
int l,r;
int sum;
}tr[N*4];
int n,m;
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void build(int u,int l,int r)
{
if(l==r) tr[u]={l,r,w[r]};
else
{
tr[u]={l,r};
int mid=tr[u].l+tr[u].r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
}
int query(int u,int l,int r)
{
if(tr[u].l>=l && tr[u].r<=r) return tr[u].sum;
int mid=tr[u].l+tr[u].r>>1;
int sum=0;
if(l<=mid) sum=query(u<<1,l,r);
if(r>mid) sum+=query(u<<1|1,l,r);
return sum;
}
void modify(int u,int x,int v)
{
if(tr[u].l==tr[u].r) tr[u].sum+=v;
else
{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
build(1,1,n);
int l,r,k;
while(m--)
{
scanf("%d%d%d",&k,&l,&r);
if(k==0)
printf("%d\n",query(1,l,r));
else
modify(1,l,r);
}
return 0;
}
2.数星星
题意:
天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。
如果一个星星的左下方(包含正左和正下)有 k 颗星星,就说这颗星星是 k 级的。
给定 N个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。
输入:
第一行一个整数 N,表示星星的数目;
接下来 N 行给出每颗星星的坐标,坐标用两个整数 x,y表示;
不会有星星重叠。星星按y坐标增序给出y 坐标相同的按 xx 坐标增序给出。
思路:
本题虽然看上去是一个二维的题目,但是本题输入的数据有特点,它是按y轴从小到大输入的,并且按x轴也从小到大输入。因此当前点的高度一定是最高的,只需要要考虑x就可以了,计算x小于当前x的和就是该点的等级。因此这里用到了前缀和。可以用树状数组来做。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=32010;
int tr[N],level[N],sum[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x)
{
for(int i=x;i<N;i+=lowbit(i)) tr[i]+=1;
}
int query(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
level[query(x)]++; //先计算起前面有多少个点,再加上当前点
add(x);
}
for(int i=0;i<n;i++) cout<<level[i]<<endl;
return 0;
}
3.数列区间最大值
题意:
输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y要求你说出 X到 Y 这段区间内的最大数。
思路:
这道题要求的是区间的最大值,可以用线段树来做,直接将线段树的模板代码中的求和sum,改成求max即可,pushup操作改成求最大值操作。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <climits>
using namespace std;
const int N=100010;
struct Node
{
int l,r;
int maxv;
}tr[N*4];
int n,m;
int w[N];
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);//原pushup操作
}
}
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 = query(u << 1, l, r);
if (r > mid) maxv = max(maxv, query(u << 1 | 1, l, r));
return maxv;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&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;
}