树状数组 :
单点修改 (+ - * /)
区间修改( + - 最值)其中+ -要利用查分数组来实现
单点查询 传统方法
区间查询 最值得话就循规蹈矩的进行查找
但是如果使用区间修改过(查分数组)后再进行区间查询,则要进行计算一下
∑ni = 1A[i] = ∑ni = 1 ∑ij = 1D[j];
则A[1]+A[2]+...+A[n]
= (D[1]) + (D[1]+D[2]) + ... + (D[1]+D[2]+...+D[n])
= n*D[1] + (n-1)*D[2] +... +D[n]
所以说,树状数组对于去区间的修改很难办到,可以用数组维护差分数组,但是这样的话对区间的查询就很难办到了,因此,树状数组适合维护单点修改,单点查询和区间查询,对于区间的修改我们使用树状数组进行维护即可。
单点修改
void update(int pos,int val )
{
for(int i=pos;i<=maxn;i+=lowbit(i))
{
t[i]+=val;//如果是区间最大值则要修改一下
}
}
区间查询
void query(int l,int r)
{
int sum=0;
while(l<=r)
{
for(;r-lowbit(r)>=l;r-=lowbit(r))
{
sum+=tree[r];
}
sum+=tree[r];
r--;
}
}
区间修改 单点查询 d数组是查分数组 第I项的值则是前i项的d[i]和
void update(int l,int r,int val)
{
d[l]+=val;
d[r+1]-=val;
}
ST表求区间最大值
for(int i=1;i<=n;i++)
st[i][0]=a[i];
for(int j=0;(1<<j)<=n;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
void query(int l,int r)
{
int k=log2(r-l+1);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
线段树
线段树就是用来维护一个区间而建立的一棵树,线段树中每一个节点维护的都是一个区间
如果父节点维护的区间是[ l , r ] ,那么两个子节点s维护的区间分别为[ l,( l+r )/2 ] 和 [ ( l+r )/2+1, r ]。
push_down
这里给一个线段树维护一段区间内的和的代码
const int maxn=1e5+10;
long long a[maxn],lazy[maxn<<2];
long long tree[maxn<<2];
void push_up(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void push_down(int rt,int l,int r)
{
if(lazy[rt])
{
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
int m=(l+r)>>1;
tree[rt<<1]+=(m-l+1)*lazy[rt];
tree[rt<<1|1]+=(r-m)*lazy[rt];
lazy[rt]=0;
}
}
void build(int rt,int l,int r)
{
if(l==r)
{
tree[rt]=a[l];
return ;
}
int m=(l+r)>>1;
build(rt<<1,l,m);
build(rt<<1|1,m+1,r);
push_up(rt);
}
void update(int rt,int l,int r,int ll,int rr,int val)
{
// if(l==r)
// {
//
// tree[rt]+=val;
// return ;
// }
if(l>=ll&&rr>=r)
{
lazy[rt]+=val;
tree[rt]+=(r-l+1)*val;
return;
}
push_down(rt,l,r);
int m=(r+l)>>1;
if(m>=ll) update(rt<<1,l,m,ll,rr,val);
if(m+1<=rr) update(rt<<1|1,m+1,r,ll,rr,val);
push_up(rt);
}
long long query(int rt,int l,int r,int ll,int rr)
{
long long res=0;
// if(l==r)
// {
// return tree[rt];
// }
if(l>=ll&&r<=rr)
{
return tree[rt];
}
push_down(rt,l,r);
int m=(l+r)>>1;
if(m>=ll) res+=query(rt<<1,l,m,ll,rr);
if(m+1<=rr) res+=query(rt<<1|1,m+1,r,ll,rr);
return res;
}
主席树
主席树就是可持久化线段树,线段树经过若干次修改后,仍然能找到原来某次修改前的线段树的信息的一种数据结构
主席树空间一般开40倍左右
代码中我注释的部分是建树是先整个建成一棵树,等到以后有更新操作了在重新建另一棵树
const int maxn=1e5;
int root[maxn];//这里是用来保存第i次更新后根节点的下标的
struct node{
int l,r; //这里l和r记录的是此节点的左孩子和右孩子下标,不是它维护的区间范围
int val; //维护的值
int lazy;
node()
{
val=0;
lazy=0;
}
}tree[maxn*40];
void init()
{
fill(root,root+maxn,0);
}
void push(int rt)
{
tree[rt].val=tree[tree[rt].l].val+tree[tree[rt].r].val;
}
void push_down(int rt,int l,int r)
{
if(tree[rt].lazy)
{
tree[tree[rt].l].lazy+=tree[rt].lazy;
tree[tree[rt].r].lazy+=tree[rt].lazy;
int m=(l+r)>>1;
tree[tree[rt].l].val+=(m-l+1)*tree[rt].lazy;
tree[tree[rt].r].val+=(r-m)*tree[rt].lazy;
tree[rt].lazy=0;
}
}
//这里的x和y分别表示这一个根节点个它前一个根节点的下标
/*void build(int &x,int y,int l,int r,int p)
{
x=++cnt;
tree[cnt]=tree[y];
if(l==r)
{
tree[cnt].val=a[l];
return ;
}
int m=(l+r)>>1;
if(p<=m) build(tree[cnt].l,tree[y].l,l,m,p);
if(p>=m+1) build(tree[cnt].r,tree[y].r,m+1,r,p);
push_up(cnt);
}*/
void build(int &x,int l,int r)
{
x=++cnt;
if(l==r)
{
tree[x].val=a[l];
return ;
}
int m=(r+l)>>1;
build(tree[x].l,l,m);
build(tree[x].r,m+1,r);
push_up(x);
}
void update(int &x,int y,int l,int r,int ll,int rr,int v)
{
x=++cnt;
tree[x]=tree[y];
if(l<=ll&&rr>=r)
{
tree[x].lazy+=v;
tree[x].val+=(r-l+1)*v;
return;
}
push_down(rt,l,r);
int m=(l+r)>>1;
if(m>=ll) update(tree[x].l,tree[y].r,l,m,ll,rr,v);
if(m+1<=rr) update(tree[x].r,tree[y].r,m+1,r,ll,rr,v);
push_up(rt);
}
int query(int x,int l,int r,int ll,int rr)
{
if(l>=ll&&rr>=r)
{
return tree[x].val;
}
push_down(x,l,r);
int res=0;
int m=(r+l)>>1;
if(ll<=m) res+=query(tree[x].l,l,m,ll,rr);
if(rr>=m+1) res+=query(tree[x].r,m+1,r,ll,rr);
return res;
}
这个代码是在建树的时候每加入一个节点新建一棵树,这样方便对一个区间内的节点进行操作
const int maxn=1e5;
int root[maxn];//这里是用来保存第i次更新后根节点的下标的
struct node{
int l,r; //这里l和r记录的是此节点的左孩子和右孩子下标,不是它维护的区间范围
int val; //维护的值
int lazy;
node()
{
val=0;
lazy=0;
}
}tree[maxn*40];
void init()
{
fill(root,root+maxn,0);
}
void push(int rt)
{
tree[rt].val=tree[tree[rt].l].val+tree[tree[rt].r].val;
}
void push_down(int rt,int l,int r)
{
if(tree[rt].lazy)
{
tree[tree[rt].l].lazy+=tree[rt].lazy;
tree[tree[rt].r].lazy+=tree[rt].lazy;
int m=(l+r)>>1;
tree[tree[rt].l].val+=(m-l+1)*tree[rt].lazy;
tree[tree[rt].r].val+=(r-m)*tree[rt].lazy;
tree[rt].lazy=0;
}
}
//这里的x和y分别表示这一个根节点个它前一个根节点的下标
void build(int &x,int y,int l,int r,int p)
{
x=++cnt;
tree[cnt]=tree[y];
if(l==r)
{
tree[cnt].val=a[l];
return ;
}
int m=(l+r)>>1;
if(p<=m) build(tree[cnt].l,tree[y].l,l,m,p);
if(p>=m+1) build(tree[cnt].r,tree[y].r,m+1,r,p);
push_up(cnt);
}
void update(int &x,int y,int l,int r,int ll,int rr,int v)
{
x=++cnt;
tree[x]=tree[y];
if(l<=ll&&rr>=r)
{
tree[x].lazy+=v;
tree[x].val+=(r-l+1)*v;
return;
}
push_down(rt,l,r);
int m=(l+r)>>1;
if(m>=ll) update(tree[x].l,tree[y].r,l,m,ll,rr,v);
if(m+1<=rr) update(tree[x].r,tree[y].r,m+1,r,ll,rr,v);
push_up(rt);
}
int query(int x,int l,int r,int ll,int rr)
{
if(l>=ll&&rr>=r)
{
return tree[x].val;
}
push_down(x,l,r);
int res=0;
int m=(r+l)>>1;
if(ll<=m) res+=query(tree[x].l,l,m,ll,rr);
if(rr>=m+1) res+=query(tree[x].r,m+1,r,ll,rr);
return res;
}
参考例题
1.求第k小
#include<cstdio>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e5+5;
int cnt,root[maxn],a[maxn];
//root[i] 第i课线段树根节点的位置
//cnt 用作开辟新的树节点。
struct node{
int l,r;//左右儿子结点编号,因为不满足2*rt规律
int sum;//代表(l,r)区间上和是多少
}T[maxn*40];
vector<int> v;
int getid(int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
/** update函数介绍
l,r 代表线段树递归的区间,x代表前一棵树的节点位置,y是后面的节点位置。
在递归的过程中,将需要修改的树节点复制到新开辟节点,改变自己的sum,
也就是自加1,顺便改变上一个的孩子节点
所以传参是引用传参;//线段树区间统计,sum代表在这个区间数的个数。
*/
//update(1,n,root[i],root[i-1],getid(a[i]));
void update(int l,int r,int &x,int y,int p){
T[++cnt] = T[y];//左右son和sum都先连接
T[cnt].sum++;
x = cnt;
if(l==r) return ;
int m = (l+r)>>1;
if(m>=p) update(l,m,T[x].l,T[y].l,p);
else update(m+1,r,T[x].r,T[y].r,p);
}
/** query函数介绍
因为是查找第K小,所以在查找时候只需要看左边孩子节点,
两棵线段树sum做差,便得到这个区间的值
比如 root[R]-root[L-1] ,则代表区间 [L,R] 的数的统计
所以 S=(R线段树左孩子的sum)-(L-1线段树左孩子的sum)
如果 S>=K(第K小),所以第K小肯定在左儿子节点,
否则,右节点,并且在右边区间再找剩下的 K-S,即可。
*/
//query(1,n,root[l],root[r],k);
int query(int l,int r,int x,int y,int k){
if(l == r) return l;
int m = (l+r)>>1;
int sum = T[T[y].l].sum - T[T[x].l].sum;
if(k<=sum) return query(l,m,T[x].l,T[y].l,k);
else return query(m+1,r,T[x].r,T[y].r,k-sum);
}
int main(){
int n,m,x,y,k;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());//去重
for(int i=1;i<=n;i++)
update(1,n,root[i],root[i-1],getid(a[i]));
for(int i=1;i<=m;i++) {
scanf("%d%d%d",&x,&y,&k);
printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);//why -1
}
return 0;
}
2.题意:
一个长度为n的数组,4种操作 : (1)C l r d:区间[l,r]中的数都加1,同时当前的时间戳加1 。 (2)Q l r:查询当前时间戳区间[l,r]中所有数的和 。 (3)H l r t:查询时间戳t区间[l,r]的和 。 (4)B t:将当前时间戳置为t 。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
#define pb push_back
typedef long long ll;
const int maxn = 100009;
struct node {
int l,r;
ll sum,lazy;
}T[maxn*40];
int root[maxn],cur,tot;
ll a[maxn];
void build(int l,int r,int &pos)
{
pos = tot++;
T[pos].sum = T[pos].lazy = 0;
// T[pos].l = l,T[pos].r = r;
if(l==r)
{
T[pos].sum = a[l];
return;
}
int mid = (l+r)>>1;
build(l,mid,T[pos].l);
build(mid+1,r,T[pos].r);
T[pos].sum = T[T[pos].l].sum + T[T[pos].r].sum;
}
void update(int L,int R, int &x, int y , int l, int r, int d)//这里的x和上面的pos类似
{
x = tot++;
T[x] = T[y];
if(l>=L && r<=R)
{
T[x].sum += 1ll*(r - l + 1) * d; //这里用目标区间的L,R;因为目标区间每个值都要加上
T[x].lazy += d;
return;
}
int mid = (l+r)>>1;
if(L <= mid)
update(L, R, T[x].l, T[y].l, l, mid, d);
if(R > mid)
update(L, R, T[x].r, T[y].r, mid+1,r, d);
T[x].sum = T[T[x].l].sum + T[T[x].r].sum + 1ll*(r-l+1) * T[x].lazy;
}
ll query(int L,int R,int x,int l,int r)
{
if(L<=l && R>=r)
{
return T[x].sum;
}
ll ans = 1ll*T[x].lazy*(min(R,r)-max(L,l) + 1);
int mid = (l+r)>>1;
if(R > mid)
ans += query(L,R,T[x].r, mid+1, r);
if(L<=mid)
ans += query(L,R,T[x].l, l,mid);
return ans;
}
int main()
{
int n,m;
int flag = 0;
while(~scanf("%d%d", &n, &m))
{
if(flag)puts("");
flag = 1;
tot = 0,cur = 0;
for(int i=1; i<=n; i++)
{
scanf("%lld", &a[i]);
}
// root[0] = tot++;
build(1,n,root[0]);
while(m--)
{
char op[20];
scanf("%s" , op);
if(op[0]=='Q')
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",query(l,r,root[cur],1,n));
}
else if(op[0]=='C')
{
int l,r;
ll d;
scanf("%d%d%lld", &l, &r, &d);
cur++;
update(l,r,root[cur],root[cur-1], 1, n, d);
}
else if(op[0]=='H')
{
int l,r,t;
scanf("%d%d%d",&l,&r,&t);
printf("%lld\n",query(l,r,root[t],1,n));
}
else if(op[0]=='B')
{
scanf("%d",&cur);
}
}
}
return 0;
}