树状数组
解决的问题:区间求和;
主要模板
int lowbit(int x)
{
return x & -x;
}
void add(int x,int c)//修改单点值
{
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int sum(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
还可以当做差分数组思想用:
题目
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long ll;
int a[N];
ll t[N];
int n,m;
int lowbit(int x)
{
return x & -x;
}
void add(int x,int c)
{
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=c;
}
ll sum(int x)
{
ll res=0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
add(i,a[i]-a[i-1]);
char op[2];
int l,r,d;
while(m--)
{
cin>>op>>l;
if(op[0]=='C')
{
cin>>r>>d;
add(l,d),add(r+1,-d);
}
else
{
cout<<sum(l)<<endl;
}
}
return 0;
}
线段树
线段树可以是一把大砍刀什么都能运用到,基本上大部分的区间操作
比如说:单点修改,单点查询,区间修改,区间查询,区间开方,区间四则运算.
线段树主要形式还是一课树的形式,树上的节点表示的区间范围;
线段树tr[u]当中的u我们可以看成父节点,所以他的左儿子的序号节点就为2u即为(u>>1),右儿子的序号节点就为2u+1即为(u>>1|1)这样我们就能在tr数组中快速找到左右儿子的节点,而且我们还能运用线段树中的懒标记来进一步优化效率
懒标记:当我们的查询区间已经包含在我们的修改区间当中时即(tr[u].l>=l&&tr[u].r<=r) 我们就不需要在进行对子节点的修改,直接对该节点的懒标记加上当前要修改的值,tr[u].add+=x;(懒标记的值可以叠加)。
pushdown(对懒标记的一个运算):从上往下,用父节点的懒标记来更新子节点。
pushup :从下往上,用子节点来更新父节点,一般是子节点被修改的时需要重新更新父节点的值。
我们可以以一个题目为例子。题目入口
给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
1、C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案
要求 :区间修改,区间查询。
做法:线段树,当我们在进行区间修改时可以运用到懒标记。
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
typedef long long ll;
struct node
{
int l,r;
ll sum,add;//sum为总和,add为懒标记的值;
}tr[N*4];//线段树的定义
int w[N];
int n,m;
int pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;//更新父节点的sum值。
}
void pushdown(int u)//懒标记
{
auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.add)//该根节点还有懒标记
{
left.add+=root.add;//左子树加上根节点的懒标记;
left.sum+=(ll)(left.r -left.l+1)*root.add;//左子树加上修改的数。
right.add+=root.add;//右子树加上根节点的懒标记;
right.sum+=(ll)(right.r- right.l+1 )*root.add;//右子树加上修改的数
root.add=0;//根节点的懒标记清空为0
}
}
void build(int u,int l,int r)//建树
{
if(l==r) tr[u]={l,r,w[l],0};
else
{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int l,int r,ll d)
{
if(tr[u].l>=l&&tr[u].r<=r)//如果当前区间已经被修改包含,则该区间的子节点不会自己查询,用懒标记表示在这个区间修改的数
{
tr[u].add+=d;//懒标记可以叠加,加上这个数;
tr[u].sum+=(tr[u].r-tr[u].l+1)*d;//总和加上这段区间里的增加的数目;
}
else
{
pushdown(u);//修改当前区间要先查看是否还有懒标记,有懒标记要先清除,即加到子节点当中。
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,d);//修改区间横跨两个区间
if(r>mid) modify(u<<1|1,l,r,d);
pushup(u);//pushup一遍来更新父节点。
}
}
ll query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;//如果当前区间已经被修改包含,直接返回该区间的sum值
else
{
pushdown(u);//修改当前区间要先查看是否还有懒标记,有懒标记要先清除,即加到子节点当中。
int mid=tr[u].l+tr[u].r>>1;
ll res=0;
if(l<=mid) res=query(u<<1,l,r);//修改区间横跨两个区间
if(r>mid) res+=query(u<<1|1,l,r);
return res;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
build(1,1,n);
char op[2];
int l,r,v;
while(m--)
{
scanf("%s%d%d",op,&l,&r);
if(op[0]=='Q')
printf("%lld\n",query(1,l,r));
else
{
scanf("%d",&v);
modify(1,l,r,v);
}
}
return 0;
}