
树状数组本质是什么呢?其实是二进制数。例如6如果用二进制数来表示6=0110=4+2 , 13=1101=8+4+1 . 因此我们可以用一个c[]数组来表示管辖范围,c[13]=c[8]+c[4]+c[1]. 那么求1~13的和就不用a[1]+a[2]+…a[13]了,而是可以直接c[8]+c[4]+c[1]。
我们可以看出刚才的13是1101有8,4,1构成,倒序来写就是13=1+4+8。我们可以先把1减掉,12=4+8,再把4减掉,8=8,再把8减掉8-8=0.这样一步一步求和运算。对应的1,4,8就是末尾1对应的位置,也就是大牛博客里讲到的Lowbit=x & (-x).这里不赘言。

找子节点(程序中不涉及找子节点的过程):
12(1100)是哪些节点的父节点
1.12(1100)管辖范围是4,所以子节点在8到12之间。
2.将12-1得到11(1011,第一个子节点)
3.11(1011)的管辖范围是1,减掉得到10(1010)
4.10(1010)的管辖范围是2,减掉得到8(1000)
5.所以12的直接子节点是: 11,10
找父节点:(那么如何能把a[]数组转换为c[]数组呢)?
1.父节点一定比自己x大,需要加多少的数字,即为父节点
2.父节点-管辖范围<=x-lowbit(x)
这样看来,我们可以找到规律,如果在x末尾1后面加1,必然管辖范围就是刚刚加的1对应的十进制,减掉管辖范围,肯定不满足条件2
例如:x=1010
最后一位加1变成1011,管辖范围是1,1011-0001=1010,显然不满足<=1010-lowbit(1010)=1000
再如:x=10100
同样,往末位1的后面加1,则减掉管辖范围最小是x,不满足条件2
那么,哪一个才是比x本身大的第一个数字,且x末尾1的后面几位都不是1?
例如,x=10100
10100 + 0011 + 1 = 11000
即 x += lowbit(x)
lowbit//-x=取反+1
int lowbit(int x){ return x&(-x); }
构建父节点c[]
void update(int p,int x){ while(p<=n){ c[p]+=x; p+=lowbit(p); } }
区间求和
int sum(int p){ int sum=0; while(p>0){ sum+=c[p]; p-=lowbit(p); } return sum; }
P3374 【模板】树状数组 1
如题,已知一个数列,你需要进行下面两种操作:
-
将某一个数加上 x
-
求出某区间每一个数的和
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> using namespace std; int n,m,c[500005]; int lowbit(int x){ return x & (-x); } void add(int i,int x){ while(i <= n){ c[i] += x; i += lowbit(i); } } int sm(int i){ int sum = 0; while(i){ sum += c[i]; i -= lowbit(i); } return sum; } int main(){ scanf("%d%d",&n,&m); int v; for(int i = 1; i <= n; i++){ scanf("%d",&v); add(i,v); } int p,x,y,sl,sr; for(int i = 1; i <= m; i++){ scanf("%d%d%d",&p,&x,&y); if(p == 1){ add(x,y); } if(p == 2){ sr = sm(y); sl = sm(x - 1); printf("%d\n",sr - sl); } } return 0; }
241. 楼兰图腾
分别统计i位置左边比a[i]小的数的个数m、右边比a[i]小的数的个数n,运用乘法原理:
1. 第一步从左边m个数中任选一个,有m种选法
2. 第二步从右边n个数中任选一个,有n种选法
权值树状数组,统计比自己大的和。
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
int c[200005], a[200005], n, h[200005], l[200005];
ll res, ans;
int lowbit(int x){
return x & (-x);
}
void add(int i, int x){
for(; i <= n; i += lowbit(i))
c[i] += x;
}
int sum(int i){
int sm = 0;
for(; i; i -= lowbit(i))
sm += c[i];
return sm;
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
for(int i = 1; i <= n; i++){
h[i] = sum(n) - sum(a[i]);
l[i] = sum(a[i]-1);
add(a[i], 1);
}
memset(c, 0, sizeof c);
for(int i = n; i >= 1; i--){
ans += 1ll * h[i] * (sum(n) - sum(a[i]));
res += 1ll * l[i] * sum(a[i]-1);
add(a[i], 1);
}
printf("%lld %lld",ans, res);
return 0;
}
acwing

输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
Q 4
Q 1
Q 2
C 1 6 3
Q 2
输出样例:
4
1
2
5
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m, a[100005], p;
ll c[100005];
char s[3];
int lowbit(int i){
return i & (-i);
}
void add(int i, int x){
for(; i <= n; i += lowbit(i)){
c[i] += x;
}
}
ll sum(int i){
ll sm = 0;
for(; i; i -= lowbit(i)){
sm += c[i];
}
return sm;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++){
scanf("%d",&a[i]);
}
for(int i = 1; i <= n; i++){
add(i, a[i] - a[i-1]);
}
for(int i = 1; i <= m; i++){
int l,r,x;
scanf("%s",s);
if(s[0] == 'Q'){
scanf("%d",&p);
printf("%lld\n",sum(p));
}
else{
scanf("%d%d%d",&l,&r,&x);
add(l, x);
add(r+1, -x);
}
}
return 0;
}
MIS
题目描述
长度为m的递增子序列(M length Increase Subsequence),称为MIS。
求长度为n的序列,有多少个MIS。
子序列不需要在原序列中不需要连续,但要保证相对位置。
输入格式
第一行两个整数n和m
第二行n个整数,表示一个序列。
输出格式
输出MIS的个数,结果对20140921取余。
样例
输入
5 3
1 2 5 3 4
输出
5
数据范围与提示
样例解释:[1 2 5],[1 2 3],[1 2 4],[1 3 4],[2 3 4] 都是MIS。
对于30%的数据,n的范围[1,30],m的范围[1,10];
对于60%的数据,n的范围[1,500],m的的范围[1,50];
对于80%的数据,n的范围[1,1000],m的的范围[1,100],序列中的元素范围[1,n];
对于100%的数据,n的范围[1,10000],m的的范围[1,100],序列中的元素范围[1,109];
比较容易想到60分的做法:</br> g[i][k]表示第i位在此上升子序列中排k名的方案数</br> 则: g[i][k] += g[j][k-1], 其中a[i] > a[j], g[j][k-1]表示第j位的数字在此上升子序列中排k-1名 </br> ``` #include<bits/stdc++.h> #define int long long using namespace std; int n, m, a[5005], g[5005][5005], ans, res; signed main(){ scanf("%lld%lld",&n,&m); for(int i = 1; i <= n; i++) scanf("%lld",&a[i]); for(int i = 1; i <= n; i++) g[i][1] = 1; for(int i = 1; i <= n; i++){ for(int j = 1; j < i; j++){ for(int k = 2; k <= m; k++){ if(a[i] > a[j]){ g[i][k] += g[j][k-1]; } } } res = (res + g[i][m]) % 20140921; } printf("%lld\n",res % 20140921); return 0; } ``` 优化:考虑如果g[i][k]累加的过程如果可以log级别查询到(求和),则总时间复杂度降为nlogn</br> 思考:当枚举到第p位时,在p-1位以前,比a[p]小且在此子序列中排名j的方案数之和,用树状数组维护"和":</br> 修改:数值i在子序列中排名为j的树状数组f[i][j]依次向上累加其方案数。</br> ``` void add(int i, int j, int x){ for(; i <= n; i += lowbit(i)){ g[i][j] = (g[i][j] + x) % 20140921; } } ``` 查询:查询权值小于等于i,且排名为j的方案数 ``` int query(int i, int j){ int sum = 0; for(; i; i -= lowbit(i)){ sum = (sum + g[i][j]) % 20140921; } return sum % 20140921; } ``` 权值树状数组,需要用到离散化。</br> 完整代码如下:</br> ``` #include<bits/stdc++.h> #define ll long long using namespace std; int g[10005][105], n, m, a[10005], b[10005]; int lowbit(int x){ return x & (-x); } void add(int i, int j, int x){ for(; i <= n; i += lowbit(i)){ g[i][j] = (g[i][j] + x) % 20140921; } } int query(int i, int j){ int sum = 0; for(; i; i -= lowbit(i)){ sum = (sum + g[i][j]) % 20140921; } return sum % 20140921; } int main(){ scanf("%d%d",&n,&m); for(int i = 1; i <= n; i++){ scanf("%d",&a[i]); b[i] = a[i]; } sort(b+1, b+n+1); int k = unique(b+1, b+n+1) - (b+1); for(int i = 1; i <= n; i++){ int x = lower_bound(b+1, b+k+1, a[i]) - b; add(x,1,1);//权值下标,排位,+1。每个数字自己作为上升序列的第一个位,其中排名就为1 for(int j = 2; j <= m; j++){ int sum = query(x-1, j-1);//查询权值比x小的,且排名为j-1的方案数 add(x, j, sum);//权值x排名为j的下标,排j-1位,+sum } } printf("%d\n",query(k,m) % 20140921); return 0; } ```
1376 最长递增子序列的数量
http://www.51nod.com/Challenge/Problem.html#problemId=1376
数组A包含N个整数(可能包含相同的值)。设S为A的子序列且S中的元素是递增的,则S为A的递增子序列。如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS)。A的LIS可能有很多个。例如A为:{1 3 2 0 4},1 3 4,1 2 4均为A的LIS。给出数组A,求A的LIS有多少个。由于数量很大,输出Mod 1000000007的结果即可。相同的数字在不同的位置,算作不同的,例如 {1 1 2} 答案为2。
输入
第1行:1个数N,表示数组的长度。(1 <= N <= 50000) 第2 ~ N+1行:每行1个数A[i],表示数组的元素(0 <= A[i] <= 10^9)
输出
输出最长递增子序列的数量Mod 1000000007。
输入样例
5
1
3
2
0
4
输出样例
2
https://dandelioncloud.cn/article/details/1453017587545395202
//单点修改, 区间维护两个值,LIS以及最大 // #include<bits/stdc++.h> #include<iostream> #include<cstdio> #define N 500005 using namespace std; const int P = 1e9+7; struct Node{ int len, cnt; }c[N], dp[N]; int a[N], b[N], n, k; int lowbit(int x){ return x & (-x); } void update(Node &tmp, Node vl){ if(tmp.len == vl.len){ tmp.cnt += vl.cnt, tmp.cnt %= P; }else if(tmp.len < vl.len){ tmp = vl; } } void add(int i, Node vl){ for(; i <= k; i += lowbit(i)){ update(c[i], vl); } } Node query(int i){ Node mx = (Node){0, 1};//一开始写的(Node){0,0} for(; i; i -= lowbit(i)){ update(mx, c[i]); } return mx; } int main(){ scanf("%d",&n); for(int i = 1; i <= n; i++){ scanf("%d",&a[i]); b[i] = a[i]; } sort(b+1, b+n+1); k = unique(b+1, b+n+1) - (b+1); for(int i = 1; i <= n; i++){ a[i] = lower_bound(b+1, b+k+1, a[i]) - b; dp[i] = query(a[i]-1); dp[i].len++; add(a[i], dp[i]); //单点修改: 以它结尾的LIS长度, 相同长度的序列个数1 } Node ans = query(k);//query(n)一开始写的n printf("%d\n", ans.cnt); // Node ans = (Node){0,0}; // for(int i = 1; i <= n; i++){ // update(ans, dp[i]); // } // printf("%d\n",ans.cnt); return 0; }
洛谷 P5156 [USACO18DEC]Sort It Out P
省选模拟题 https://www.luogu.com.cn/problem/solution/P5156