题意:给N个数,然后给出N个数对应的数值,然后对叶子结点进行add,sub,query操作 输入end时即寻问结束
思路:对于RMQ问题当然使用线段树比较方便,同时这道题也是最简单的线段树(因为只对叶子结点进行修改,而没有对区间进行修改)对结点进行sub(减)操作即使 加入一个相反数
然后这道题主要是 对建树(build) ,结点更新(update),以及区间查询(query) ,由于没有对区间进行修改所以不需要使用push_down().
建树:开空间是maxn(最大结点数的四倍):就是计算整个满二叉树的结点数,log2N为高度 ,从底层往上每次结点数减半(等比数列) 所以最后利用等比数列公式推导近似等于4N
然后就是递归建树:即使将总区间一直二分减小,直到不能减小时即找到了叶子结点位置
更新:也是同理,递归找到结点修改,然后再push_up()向上更新整棵树
完整题解:
//对于线段树来说某人1~mid/2,mid/2+1~n为一个区间 #include <iostream> #define rep(i,n) for(int i=1;i<=n;i++) //( 0<N<=200000,0<M<5000 ) using namespace std; const int maxn = 2e5+1; int seg[maxn<<2]; int arr[maxn]; inline int ls(int x){ return x<<1; } inline int rs(int x){ return x<<1|1; } inline void push_up(int p) { seg[p]=seg[ls(p)]+seg[rs(p)]; } //基础建树 void build(int p,int l,int r){//p为下标 if(l==r) { seg[p] = arr[l];//左右相等时即意味着找到了结点位置 return ; } int mid = (l+r)>>1; build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p);//将p点向上更新结点 } //对于单点更新来说就是找到更新点坐标然后去push_up(向上更新) //也就是要先找到点坐标 p为下标,k为修改的值,q为要增加的大小 void update(int k,int p,int l,int r,int q){ if(k==seg[p]&&l==r) { seg[p] += q; return; } int mid = (l+r)>>1; if(mid<k) update(k,rs(p),mid+1,r,q);//递归寻找 else update(k,ls(p),l,mid,q); push_up(p); } int query(int qx,int qy,int l,int r,int p){ int res = 0; if(qx<=l&&r<=qy) return seg[p];//即寻找到该区间 int mid = (l+r)>>1; if(qx<=mid) res+=query(qx,qy,l,mid,ls(p)); if(qy>mid) res+=query(qx,qy,mid+1,r,rs(p)); return res; } int main(){ int T,n; int cnt =0; scanf("%d",&T); while(T--){ int a1,b,c,d,e,f; cin>>n; rep(i,n){ cin>>arr[i]; } build(1,1,n); string s; cout<<"Case "<<++cnt<<":"<<endl; while(cin>>s){ if(s=="End") break; else { if(s=="Query"){ scanf("%d%d",&e,&f); printf("%d ",query(e,f,1,n,1)); }else if(s=="Add"){ scanf("%d%d",&b,&d);//在b点加d update(b,1,1,n,d); }else{ scanf("%d%d",&b,&d);//区间减法相当于加法的相反数 update(b,1,1,n,-d); } } } } }
别人的模板修改:(这个有区间修改)
#include<iostream> #include<cstdio> #define ll long long using namespace std; const int MAXN = 5e5+1; unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2]; inline ll ls(ll x) { return x<<1; } inline ll rs(ll x) { return x<<1|1; } void scan() { cin>>n;//1 for(ll i=1;i<=n;i++) scanf("%lld",&a[i]); } inline void push_up(ll p) { ans[p]=ans[ls(p)]+ans[rs(p)]; } void build(ll p,ll l,ll r) { tag[p]=0; if(l==r){ans[p]=a[l];return ;} ll mid=(l+r)>>1; build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p); } inline void f(ll p,ll l,ll r,ll k) { tag[p]=tag[p]+k; ans[p]=ans[p]+k*(r-l+1); } inline void push_down(ll p,ll l,ll r) { ll mid=(l+r)>>1; f(ls(p),l,mid,tag[p]); f(rs(p),mid+1,r,tag[p]); tag[p]=0; } inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k) { if(nl<=l&&r<=nr) { ans[p]+=k*(r-l+1); tag[p]+=k; return ; } push_down(p,l,r); ll mid=(l+r)>>1; if(nl<=mid)update(nl,nr,l,mid,ls(p),k); if(nr>mid) update(nl,nr,mid+1,r,rs(p),k); push_up(p); } ll query(ll q_x,ll q_y,ll l,ll r,ll p) { ll res=0; if(q_x<=l&&r<=q_y)return ans[p]; ll mid=(l+r)>>1; push_down(p,l,r); if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p)); if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p)); return res; } int main(){ int T; int cnt =0; scanf("%d",&T); while(T--){ ll a1,b,c,d,e,f; scan(); build(1,1,n); string s; cout<<"Case "<<++cnt<<":"<<endl; while(cin>>s){ if(s=="End") break; else { if(s=="Query"){ scanf("%lld%lld",&e,&f); printf("%lld ",query(e,f,1,n,1)); }else if(s=="Add"){ scanf("%lld%lld",&b,&d);//1 x y k 含义:将区间[x,y]内每个数加上k update(b,b,1,n,1,d); }else{ scanf("%lld%lld",&b,&d);//区间减法相当于加法的相反数 update(b,b,1,n,1,-d); } } } } }