RMQ问题:区间最大值或最小值
操作:求区间最值、修改元素
区间和问题:修改操作在求和
线段树:用于区间处理的数据结构,二叉树构建,当查找点或者区间的时候,顺着节点往下找,最多log2n次就能找到,用了二叉树折半查找
!修改和查询可以用一起做,所以复杂度是O(mlog2n),m次操作
点修改:
poj last cows
方法1:暴力O(N^2)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; //线段树 //知道排在他前面的数比它小的有多少个 int pre[8100]; int ans[8100]; int num[8100]; //剩下的编号中pre[n]+1大的编号就是ans[n] //暴力:从pre的末尾开始算,没处理完一头牛,就需要重新排序,重新拍的时候可以做下一次查找,所以复杂度为O(n^2) int n; int main(){ scanf("%d",&n); pre[1]=0; for(int i=1;i<=n;i++) num[i]=i; for(int i=2;i<=n;i++) scanf("%d",&pre[i]); for(int i=n;i>=1;i--){ int k=0; for(int j=1;j<=n;j++){ if(num[j]!=-1) k++; if(k==pre[i]+1){ ans[i]=num[j]; num[j]=-1; break; } } } for(int i=1;i<=n;i++) printf("%d ",ans[i]); return 0; }
方法2:线段树
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10000; const int INF=0x3fffffff; typedef long long LL; //存储空间:4*n //复杂度: //线段树吧n个数按照二叉树进行分组,每次更新有关节点时,这个节点下面的所有子节点都隐含被更新了,从而减少了操作次数 struct node{ int l,r; int len; }tree[4*maxn]; void build(int left,int right,int y){ tree[y].l=left; tree[y].r=right; tree[y].len=right-left+1; if(left==right) return; build(left,(left+right)>>1,y<<1); //左子树 build(((left+right)>>1)+1,right,(y<<1)+1); } int que(int u,int num){ //查询+维护:所求为当前区间中坐起第num个元素 tree[u].len--; //对方问到的区间都进行处理 //如果找到了(叶子) if(tree[u].l==tree[u].r) return tree[u].l; if(tree[u<<1].len<num) { return que((u<<1)+1,num-tree[u<<1].len); //左子区间个数不够 ,查询第右区间第 num-tree[u<<1].len个元素 } if(tree[u<<1].len>=num) return que(u<<1,num); //左子区间够,就往左查 } int pre[maxn]; int ans[maxn]; int main(){ int n; scanf("%d",&n); pre[1]=0; for(int i=2;i<=n;i++) scanf("%d",&pre[i]); build(1,n,1); for(int i=n;i>=1;i--){ //从后往前推出每次最后一位数字 ans[i]=que(1,pre[i]+1); } for(int i=1;i<=n;i++){ printf("%d ",ans[i]); } return 0; }
用完全二叉树实现:
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10000; const int INF=0x3fffffff; typedef long long LL; //用完全二叉树实现线段树 int pre[maxn],ans[maxn],tree[maxn*4];//tree村的是数量 int n; void build(int n,int last_left){ int i; for(i=last_left;i<last_left+n;i++) tree[i]=1; //从二叉树的最后一行倒推到根节点,根节点的值是牛的数量 while(last_left!=1){ for(i=last_left/2;i<last_left;i++) tree[i]=tree[i*2]+tree[i*2+1]; last_left=last_left/2; } } int que(int u,int num,int last_left){ //查询+维护,所求值为当前区间左起第num个元素 tree[u]--; if(tree[u]==0&&u>=last_left) return u; //查到底了 if(tree[u<<1]<num) { //左子区间个数不够了,查询右子区间坐起第num-tree[u<<1].len个元素 return que((u<<1)+1,num-tree[u<<1],last_left); } if(tree[u<<1]>=num) return que(u<<1,num,last_left); } int main(){ scanf("%d",&n); pre[1]=0; for(int i=2;i<=n;i++) scanf("%d",&pre[i]); int last_left=1<<(int(log(n)/log(2))+1); //二叉树最后一行的最左边的一个,计算方法是找到离2最近的2的指数 build(n,last_left); for(int i=n;i>=1;i--){ ans[i]=que(1,pre[i]+1,last_left)-last_left+1; } for(int i=1;i<=n;i++) printf("%d ",ans[i]); return 0; }
区间修改:
加:把区间ai.....aj的值全部加上v
查询:查询L,R之间所有的和
lazy_tag方法:当修改的是一个整块的区间时,只对这个区间进行整体上的修改,其内部元素不需要改变,但是当这部分线段的一致性被破坏时,才会把变化值传递给子区间
查询也是一样
有错
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; //有错 const int maxn=1e5+5; const int INF=0x3fffffff; typedef long long LL; LL summ[maxn<<2],add[maxn<<2]; //4倍空间 void pusup(int r){ summ[r]=summ[r<<1]+summ[r<<1|1]; } void push_down(int rt,int m){ //更新r的子节点,m为长度 if(add[rt]){ add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; summ[rt<<1]+=(m-(m>>1))*add[rt]; summ[rt<<1|1]+=(m>>1)*add[rt]; add[rt]=0; } } void build(int l,int r,int rt){ add[rt]=0; if(l==r) { scanf("%lld",&summ[rt]); return; } int mid=(l+r)/2; build(l,mid-1,rt<<1); build(mid+1,r,rt<<1|1); pusup(rt); //这是个递归结构 } void update(int a,int b,LL c,int l,int r,int rt){ //区间更新, if(a<=l&&b>=r){ summ[rt]+=(r-l+1)*c; add[rt]+=c; return; } push_down(rt,r-l+1); //向下更新 int mid=(l+r)/2; if(a<=mid) update(a,b,c,l,mid-1,rt<<1); //分成两半,继续深入 if(b>mid) update(a,b,c,mid+1,r,rt<<1|1); pusup(rt); //向上更新 } LL que(int a,int b,int l,int r,int rt){ //区间求和 if(a<=l&&b>=r) return summ[rt]; //满足lazy直接返回 push_down(rt,r-l+1); int mid=(l+r)/2; LL ans=0; if(a<=mid) ans+=que(a,b,l,mid-1,rt<<1); if(b>mid) ans+=que(a,b,mid+1,r,rt<<1|1); return ans; } int main(){ int n,m; scanf("%d %d",&n,&m); build(1,n,1); //先建树 while(m--){ string str; int a,b; LL c; cin>>str; if(str[0]=='C'){ scanf("%d %d %lld",&a,&b,&c); update(a,b,c,1,n,1); } else{ scanf("%d %d",&a,&b); printf("%lld ",que(a,b,1,n,1)); } } return 0; }
树状数组BIT
利用二进制特征进行检索的树状结构
lowbit运算:找到x的二进制数的最后1个1
#define lowbir(x) ((x)&-(-x)) void add(int x,int d){ //更新数组tree[] while(x<=n){ tree[x]+=d; x+=lowbit(x); } } //求和 int summ(int x){ int ans=0; while(x>0){ ans+=tree[x]; x-=lowbit(x); } return ans; }
tree[x]就是x前面lowbit(x)个数相加的结果
再次计算2182题,last cows
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10010; const int INF=0x3fffffff; typedef long long LL; #define lowbit(x) ((x)&(-x)) int tree[maxn],pre[maxn],ans[maxn]; int n; void add(int x,int d){ //更新数组tree[] while(x<=n){ tree[x]+=d; x+=lowbit(x); } } //求和 int summ(int x){ int ans=0; while(x>0){ ans+=tree[x]; x-=lowbit(x); } return ans; } int findpos(int x){ //寻找sum(x)=pre[i]+1所对应的x,就是第x头牛 int l=1,r=n; while(l<r){ int mid=(l+r)>>1; if(summ(mid)<x) l=mid+1; else r=mid; } return l; } int main(){ scanf("%d",&n); pre[1]=0; for(int i=2;i<=n;i++){ scanf("%d",&pre[i]); } for(int i=1;i<=n;i++){ tree[i]=lowbit(i); //这个题目特殊,不需要add初始化,直接用lowbit就可以了 } for(int i=n;i>=1;i--){ int x=findpos(pre[i]+1); add(x,-1); //更新tree数字,减少一个 ans[i]=x; } for(int i=1;i<=n;i++) printf("%d ",ans[i]); return 0; }