赞
踩
引入问题:
给出一个长度为n的数组,完成以下两种操作:
1. 将第i个数加上k
2. 输出区间[i,j]内每个数的和
朴素算法:
单点修改:O( 1 )
区间查询:O( n )
使用树状数组:
单点修改:O( logn )
区间查询:O( logn )
前置知识:
lowbit()
运算:非负整数x在二进制表示下最低位1及其后面的0构成的数值。
如lowbit(12)= 1100 = 4
函数实现:
- int lowbit(int x){
- return x&(-x);
- }
树状数组的主要思想就是利用树结构维护“前缀和”。
对于一个序列,对其建立如下树形结构:
1.每个结点t[x]保存以x为根的子树中叶结点值的和
2.每个结点覆盖的长度为lowbit(x)
3.t[x]结点的父结点为t[x + lowbit(x)]
4.树的深度为log2n+1
add(x, k)表示将序列中第x个数加上k。
在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确。
- void add(int x, int k)
- {
- for(int i = x; i <= n; i += lowbit(i))
- t[i] += k;
- }
ask(x)表示将查询序列前x个数的和
以ask(7)为例:
查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x),例如 7 - lowbit(7) = 6
- int ask(int x)
- {
- int sum = 0;
- for(int i = x; i; i -= lowbit(i))
- sum += t[i];
- return sum;
- }
从左向右依次遍历每个数a[i],使用树状数组统计在i位置之前所有比a[i]大的数的个数、以及比a[i]小的数的个数。
统计完成后,将a[i]加入到树状数组。
从右向左依次遍历每个数a[i],使用树状数组统计在i位置之后所有比a[i]大的数的个数、以及比a[i]小的数的个数。
统计完成后,将a[i]加入到树状数组。
代码附上:
- #include <bits/stdc++.h>
- #define int long long
- using namespace std;
- const int N = 2e5+5;
- int tr[N];
- int low[N],great[N];
- int a[N];
- int n;
-
- 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;
- }
- return;
- }
-
- int ask(int x){
- int res=0;
- for(int i=x;i>=1;i-=lowbit(i)){
- res+=tr[i];
- }
- return res;
- }
-
- signed main(){
- ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
- cin>>n;
- for(int i=1;i<=n;i++)cin>>a[i];
-
- for(int i=1;i<=n;i++){//记录i点左边的信息
- int y=a[i];
- low[i]=ask(y-1);//low[i]记录左边[1,y-1]区间的数字个数
- great[i]=ask(n)-ask(y);//great[i]记录左边[y+1,n]区间的数字个数
- add(y,1);//加入树状数组中,出现一次+1
- }
-
- memset(tr,0,sizeof(tr));//初始化
- int ans1=0,ans2=0;
-
- for(int i=n;i>=1;i--){//记录i点右边的信息
- int y=a[i];
- ans2+=low[i]*ask(y-1);//乘法原理
- ans1+=great[i]*(ask(n)-ask(y-1));
- add(y,1);
- }
-
- cout<<ans1<<" "<<ans2<<"\n";
-
- return 0;
- }
区间修改 + 求单点
那么这道题目可以采用差分的方法去做,在上一道题目中我们求的是区间和,采用前缀和的思想去做,那么这里我们可以把单点a[i]看成前缀和,因为a[i]=差分和。
首先我们要先建差分数组:
- for(int i=1;i<=n;i++){
- add(i,a[i]-a[i-1]);
- }
那么接下来的情况就跟第一题一样了
代码附上:
- #include <bits/stdc++.h>
- #define int long long
- using namespace std;
- const int N =1e5+5;
- int a[N],tr[N],low[N],great[N];
- int n,q;
-
- 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;
- }
- return;
- }
-
- int ask(int x){
- int res=0;
- for(int i=x;i>=1;i-=lowbit(i)){
- res+=tr[i];
- }
- return res;
- }
-
- signed main(){
- ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
- cin>>n>>q;
- for(int i=1;i<=n;i++)cin>>a[i];
-
- for(int i=1;i<=n;i++){
- add(i,a[i]-a[i-1]);
- }
-
- while(q--){
- char op;cin>>op;
- if(op=='C'){
- int l,r,d;cin>>l>>r>>d;
- add(l,d);
- add(r+1,-d);
- }
- else if(op=='Q'){
- int l;cin>>l;
- cout<<ask(l)<<"\n";
- }
- }
-
- return 0;
- }
区间查询+区间修改
直接上图:
所以这道题目我们需要维护两个树状数组,一个是d[i],另一个是i*d[i]。
- #include <bits/stdc++.h>
- #define int long long
- using namespace std;
- const int N = 1e5+5;
- int n,m;
- int a[N];
- int tr[N],tr2[N];
- //tr[]数组是原始数组的差分数组d[i]的树状数组
- //tr2[]数组是原始数组的差分数组乘以i即i*d[i]的树状数组
-
- int lowbit(int x){
- return x&-x;
- }
-
- void add1(int x,int c){
- for(int i=x;i<=n;i+=lowbit(i)){
- tr[i]+=c;
- }
- return;
- }
-
- void add2(int x,int c){
- for(int i=x;i<=n;i+=lowbit(i)){
- tr2[i]+=c;
- }
- return;
- }
-
- int ask1(int x){
- int res=0;
- for(int i=x;i>=1;i-=lowbit(i)){
- res+=tr[i];
- }
- return res;
- }
-
- int ask2(int x){
- int res=0;
- for(int i=x;i>=1;i-=lowbit(i)){
- res+=tr2[i];
- }
- return res;
- }
-
- int get_sum(int x){//最后一步的推导
- return ask1(x)*(x+1)-ask2(x);
- }
-
- signed main(){
- ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
- cin>>n>>m;
- memset(tr2,0,sizeof(tr2));
- memset(tr,0,sizeof(tr));
- for(int i=1;i<=n;i++)cin>>a[i];
-
- for(int i=1;i<=n;i++){
- int b=a[i]-a[i-1];
- add1(i,b);
- add2(i,b*i);
- }
-
- while(m--){
- char op;cin>>op;
- if(op=='Q'){
- int l,r;cin>>l>>r;
- cout<<get_sum(r)-get_sum(l-1)<<"\n";
- }
- else if(op=='C'){
- int l,r,d;cin>>l>>r>>d;
- add1(l,d),add1(r+1,-d);
- add2(l,l*d),add2(r+1,(r+1)*(-d));
- }
- }
-
- return 0;
- }
仅仅根据题目的信息,很难找出他们之间的高度关系。面对这种题目,我们可以从边界去思考,(正着不行就反着来),从最后一头牛看,最后一头牛的a[i]表示前i头牛有x个比它矮的,那么也就意味着它排第x+1位,那么从倒数第二头牛来看,前面有y头比它矮的,那最后一头牛可能比它高也可能比它矮,因此,对于倒数第二头牛而言,它应该在除去上述x+1的区间[1,n]中,选取A(n−1)+1小的数。其他的牛以此类推。
假如建立一个全部元素为1的数列,某个位置的数为1代表这个高度还不知道是哪头牛的,那么就用树状数组维护该数列的前缀和,若某个位置的前缀和等于A(i)+1此时的下标就是要找的数。选择这个数后,将相应位置的1置0.可以二分这个位置。
- #include <bits/stdc++.h>
- #define int long long
- using namespace std;
- const int N = 1e5+5;
- int a[N],ans[N],tr[N];
- int n;
-
- 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;
- }
- return;
- }
-
- int ask(int x){
- int res=0;
- for(int i=x;i>=1;i-=lowbit(i)){
- res+=tr[i];
- }
- return res;
- }
-
- signed main(){
- ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
- cin>>n;
- a[1]=0;
- for(int i=2;i<=n;i++)cin>>a[i];
- for(int i=1;i<=n;i++)tr[i]=lowbit(i);//初始化
-
- for(int i=n;i>=1;i--){//从后往前枚举
- int k=a[i]+1;//找剩余的牛中高度排第k
- int l=1,r=n;
- while(l<r){
- int mid=(l+r)/2;
- if(ask(mid)>=k)r=mid;
- else l=mid+1;
- }
- ans[i]=r;
- add(r,-1);//高度排第r的牛去掉
- }
-
- for(int i=1;i<=n;i++){
- cout<<ans[i]<<"\n";
- }
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。