树状数组的数据结构,它能在O(logn)内对数组的值进行修改和查询某一段数值的和。
区间更新,单点求和的做题心路:
首先是HDU 1556
算是做树状数组的第一个模板例题,然后看着板子,竟然写错了,还是没有理解板子的意思
这里我写成这样的了,可是这样的话,时间复杂度比直接for循环还要大,看了别人的代码后发现,我理解错了,输入y以后,直接就对前面出现的数据进行更新了!
然后就改改改,仿照模板继续写!
这个是单点操作的板子:
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int num)
{
while(x<=N)
{
d[x]+=num;
x+=lowbit(x);
}
}
int getSum(int x)
{
int s=0;
while(x>0)
{
s+=d[x];
x-=lowbit(x);
}
return s;
}
一开始看得板子只有但个点的插入,然后就是某一个区间的求和操作,如果这样做的话,就要for循环了,后来发现这道题能够直接对区间进行操作,只需要改变一下就行了。
模板:
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<map>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=100005;
int n;
int in[maxn];//这个代表的是每个的数据
int a,b;
int lowbit(int x)
{
return x&(-x);
}
void update(int i,int x)
{
while(i>0)
{
in[i]+=x;
i-=lowbit(i);
}
}
int sum(int x)
{
int sum=0;
while(x<=n)
{
sum+=in[x];
x+=lowbit(x);
}
return sum;
}
int main()
{
while(~scanf("%d",&n)&&n)
{
memset(in,0,sizeof(in));
for(int i=1;i<=n;++i)
{
scanf("%d %d",&a,&b);
update(b,1);
update(a-1,-1);
}
printf("%d",sum(1));
for(int i=2;i<=n;++i)
printf(" %d",sum(i));
printf("\n");
}
return 0;
}
不过,这道题可以用前缀和来写,并且发现还特别特别的简单!
前面的时候就有接触前缀和,只是对输入数据的第一位和最后一位的后一位进行一个操作,最后每一次的这一位加上前一个的操作就行了!
关键点是,输入的第一个数据进行的操作是这一位++,输入的第二个数据济宁的操作是后面的一位--
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=100005;
int mapp[maxn];
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
memset(mapp,0,sizeof(mapp));
for(int i=1;i<=n;++i)
{
int a,b;
scanf("%d%d",&a,&b);
mapp[a]++;
mapp[b+1]--;
}
//对数据的开头进行操作
for(int i=1;i<=n;++i)
{
mapp[i]+=mapp[i-1];
if(i==1)
printf("%d",mapp[i]);
else
printf(" %d",mapp[i]);
if(i==n)
printf("\n");
}
}
return 0;
}
解决线段树花费了一段时间
因为一般如果说是对单个点进行操作很简单,现在变成区间的更新,需要引入懒惰标记
可算是过了。。。
感觉现在理解起来,只能说玄而又玄!!!!
首先呢,是数组的开辟,应该开辟的数组大小时数据范围的四倍。
int sum[maxn<<2];
int add[maxn<<2];//懒惰标记
然后呢,是建树,懵逼的是为什么建树的时候进行输入,这道题压根不需要输入的,只需要在建树的时候把数据初始化为0就行了。
void build(int l,int r,int rt)
{
sum[rt]=0;
if(l==r) return ;
int m=(l+r)>>1;
build(lson);
build(rson);
}
其中:
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
接着,就是更新数据了。对应的函数是update。
这个update就跟树状数组一样,有两种操作,单点操作和区间操作。
单点操作:
单个点的修改
void update(int L,int C,int l,int r,int rt)
{
//l,r,rt当前节点的区间,以及当前节点的编号
//到了叶节点,进行修改
if(l==r)
{
sum[rt]+=C;
return ;
}
int m=(l+r)/2;
if(L<=m)
update(L,C,lson);
else
update(L,C,rson);
//子节点更新,父节点更新
pushup(rt);
}
//区间上的修改
这里出现了一个pushup
void pushup(int x)
{
sum[x]=sum[x<<1]+sum[x<<1|1];
}
这里改变的的儿子的值。。。
区间操作:
void update(int L,int R,int C,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
//如果说本区间完全在操作区间以内
sum[rt]+=C*(r-l+1);
add[rt]+=C;
return ;
}
int m=(l+r)>>1;
pushdown(rt,m-l+1,r-m);
if(m>=L) update(L,R,C,lson);
if(m<R) update(L,R,C,rson);
pushup(rt);
}
这里出现了一个pushdowm
void pushdown(int rt,int l,int r)
{
if(add[rt])
{
add[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
//求出两个节点的值
sum[rt<<1]+=add[rt]*l;
sum[rt<<1|1]+=add[rt]*r;
//标记下传,置0
add[rt]=0;
}
}
懒惰标记
现在数据更新完毕,接下来就是输出了。
在以前接触线段树的时候,输出的是某一个区间的和,这个时候只需输入一个区间,就可以得出答案了,那么现在也可以用这个思维,把区间的左右端点都设置为i,这样就是单个点的值了!
int query(int L,int R,int l, int r,int rt)
{
if(L<=l&&r<=R)
{
return sum[rt];
}
int m=(l+r)>>1;
pushdown(rt,m-l+1,r-m);
int ans=0;
if(L<=m) ans+=query(L,R,lson);
if(R>m) ans+=query(L,R,rson);
return ans;
}
实现代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
//其中rt代表的是当前的节点的编号
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int maxn=100005;
int sum[maxn<<2];
int add[maxn<<2];//懒惰标记
void build(int l,int r,int rt)
{
sum[rt]=0;
if(l==r) return ;
int m=(l+r)>>1;
build(lson);
build(rson);
}
void pushup(int x)
{
sum[x]=sum[x<<1]+sum[x<<1|1];
}
void pushdown(int rt,int l,int r)
{
if(add[rt])
{
add[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
//求出两个节点的值
sum[rt<<1]+=add[rt]*l;
sum[rt<<1|1]+=add[rt]*r;
//标记下传,置0
add[rt]=0;
}
}
//void build(int l,int r,int rt)
//{
// if(l==r)
// {
// scanf("%d",&sum[rt]);//储存数组值
// return ;
// }
// int m=(l+r)>>1;
// //左右递归
// build(lson);
// build(rson);
// pushup(rt);//更新信息
//}
int query(int L,int R,int l, int r,int rt)
{
if(L<=l&&r<=R)
{
return sum[rt];
}
int m=(l+r)>>1;
pushdown(rt,m-l+1,r-m);
int ans=0;
if(L<=m) ans+=query(L,R,lson);
if(R>m) ans+=query(L,R,rson);
return ans;
}
//单个点的修改
//void update(int L,int C,int l,int r,int rt)
//{
// //l,r,rt当前节点的区间,以及当前节点的编号
// //到了叶节点,进行修改
// if(l==r)
// {
// sum[rt]+=C;
// return ;
// }
// int m=(l+r)/2;
// if(L<=m)
// update(L,C,lson);
// else
// update(L,C,rson);
// //子节点更新,父节点更新
// pushup(rt);
//}
区间上的修改
void update(int L,int R,int C,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
//如果说本区间完全在操作区间以内
sum[rt]+=C*(r-l+1);
add[rt]+=C;
return ;
}
int m=(l+r)>>1;
pushdown(rt,m-l+1,r-m);
if(m>=L) update(L,R,C,lson);
if(m<R) update(L,R,C,rson);
pushup(rt);
}
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
memset(sum,0,sizeof(sum));
memset(add,0,sizeof(add));
build(1,n,1);
for(int i=1;i<=n;++i)
{
int a,b;
scanf("%d%d",&a,&b);
update(a,b,1,1,n,1);
}
printf("%d",query(1,1,1,n,1));
for(int i=2;i<=n;++i)
printf(" %d",query(i,i,1,n,1));
printf("\n");
}
return 0;
}
从上往下,分别是 线段树,前缀和,树状数组。