树状数组是一种支持单点修改和区间查询的,代码量小的数据结构。
前置知识介绍:
lowbit(x)lowbit(x)lowbit(x)表示的为x的二进制表达中,最低为的1及其后面的0组成的数。
int lowbit(x){
return (x)&(-x);
}
区间查询
树状数组重要的是用前缀数组c[x]维护一些信息。假设原数组为a,则c[x]维护的就是从a中x−lowbit(x)+1到x的信息。树状数组重要的是用前缀数组c[x]维护一些信息。假设原数组为a,则c[x]维护的就是从a中x-lowbit(x)+1到x的信息。树状数组重要的是用前缀数组c[x]维护一些信息。假设原数组为a,则c[x]维护的就是从a中x−lowbit(x)+1到x的信息。
以区间和为例。想要查询a(l,r)的和?那么其实就相当于查询∑i=1rai与∑i=1l−1ai,二者做差即可。以区间和为例。想要查询a(l,r)的和?那么其实就相当于查询\sum_{i=1}^{r}a_i与\sum_{i=1}^{l-1}a_i,二者做差即可。以区间和为例。想要查询a(l,r)的和?那么其实就相当于查询∑i=1rai与∑i=1l−1ai,二者做差即可。
如何查询a(1,x)?对于c[x],我们知道其维护的是∑i=x−lowbit(x)+1xai,那么我们可以一直让x往前面跳,直到x为0。每次让x=x−lowbit(x)。如何查询a(1,x)?对于c[x],我们知道其维护的是\sum_{i=x-lowbit(x)+1}^{x}a_i,那么我们可以一直让x往前面跳,直到x为0。每次让x=x-lowbit(x)。如何查询a(1,x)?对于c[x],我们知道其维护的是∑i=x−lowbit(x)+1xai,那么我们可以一直让x往前面跳,直到x为0。每次让x=x−lowbit(x)。
int que(int x){
int sum=0;
while(x>0){
sum+=c[x];
x=x-lowbit(x);
}
return sum;
}
int que_range(int l,int r){
return que(r)-que(l-1);
}
单点修改
首先需要知道,针对ai进行单点修改,我们只需要修改所有包含ai的cx。此外,在c构成的树状数组中,x的父亲为x+lowbit(x)。且c[x]只会管辖其左下方内容。故我们每次更新x并更新对应的c[x]即可。首先需要知道,针对a_i进行单点修改,我们只需要修改所有包含a_i的c_x。此外,在c构成的树状数组中,x的父亲为x+lowbit(x)。且c[x]只会管辖其左下方内容。故我们每次更新x并更新对应的c[x]即可。首先需要知道,针对ai进行单点修改,我们只需要修改所有包含ai的cx。此外,在c构成的树状数组中,x的父亲为x+lowbit(x)。且c[x]只会管辖其左下方内容。故我们每次更新x并更新对应的c[x]即可。
void update(int idx,int x){
a[idx]+=x;
while(idx<=n){
c[idx]+=x;
idx=idx+lowbit(idx));
}
}
建树
一般转化为n次单点修改,假如a为[5,1,7],那么可以看作对原始数组c(全为0)进行三次单点修改,对a[1]单点加5,对a[2]单点加1,对a[3]单点加7即可。一般转化为n次单点修改,假如a为[5,1,7],那么可以看作对原始数组c(全为0)进行三次单点修改,对a[1]单点加5,对a[2]单点加1,对a[3]单点加7即可。一般转化为n次单点修改,假如a为[5,1,7],那么可以看作对原始数组c(全为0)进行三次单点修改,对a[1]单点加5,对a[2]单点加1,对a[3]单点加7即可。
关于区间修改与查询
首先关于前置知识:差分。差分数组是一种用来记录多次区间修改的内容的数组。对于数组a,定义其差分数组b[1]=a[1],b[i]=a[i]−a[i−1]。对于差分数组,求前缀和即可得到数组a。首先关于前置知识:差分。差分数组是一种用来记录多次区间修改的内容的数组。对于数组a,定义其差分数组b[1]=a[1],b[i]=a[i]-a[i-1]。对于差分数组,求前缀和即可得到数组a。首先关于前置知识:差分。差分数组是一种用来记录多次区间修改的内容的数组。对于数组a,定义其差分数组b[1]=a[1],b[i]=a[i]−a[i−1]。对于差分数组,求前缀和即可得到数组a。
现在假如要区间查询。同样的,我们考虑将a[l,r]转化为a[1,r]−a[1,l−1]。现在单独考虑a[1,r],∑i=1rai=∑i=1r∑j=1ibj。每个bi被计算了(r−i+1)次,故再次转化为∑i=1rbi∗(r−i+1)。这里用图来表示更清晰。现在假如要区间查询。同样的,我们考虑将a[l,r]转化为a[1,r]-a[1,l-1]。现在单独考虑a[1,r],\sum_{i=1}^{r}a_i=\sum_{i=1}^{r}\sum_{j=1}^{i}b_j。每个b_i被计算了(r-i+1)次,故再次转化为\sum_{i=1}^{r}b_i*(r-i+1)。这里用图来表示更清晰。现在假如要区间查询。同样的,我们考虑将a[l,r]转化为a[1,r]−a[1,l−1]。现在单独考虑a[1,r],∑i=1rai=∑i=1r∑j=1ibj。每个bi被计算了(r−i+1)次,故再次转化为∑i=1rbi∗(r−i+1)。这里用图来表示更清晰。
所以我们可以用两个树状数组,一个c1维护差分数组b的值,一个c2维护b[i]∗i的值。所以我们可以用两个树状数组,一个c1维护差分数组b的值,一个c2维护b[i]*i的值。所以我们可以用两个树状数组,一个c1维护差分数组b的值,一个c2维护b[i]∗i的值。
如何区间操作?假如a[l,r]区间都加上x,则b[l]+x且b[r+1]−x。那么对c1在l和r+1单点修改两次x即可,对c2在l,单点修改x∗l,在r+1,单点修改x∗(r+1)。如何区间操作?假如a[l,r]区间都加上x,则b[l]+x且b[r+1]-x。那么对c1在l和r+1单点修改两次x即可,对c2在l,单点修改x*l,在r+1,单点修改x*(r+1)。如何区间操作?假如a[l,r]区间都加上x,则b[l]+x且b[r+1]−x。那么对c1在l和r+1单点修改两次x即可,对c2在l,单点修改x∗l,在r+1,单点修改x∗(r+1)。
void solve() {
vector<int> c1(500002, 0);//维护差分数组b
vector<int> c2(500002, 0);//维护b[i]*i
vector<int> a(500002, 0);
vector<int> b(500002, 0);
int n;
cin >> n;
int m;
cin >> m;
auto update_c1 = [&](int idx, int val) {//单点更新c1
while (idx <= n) {
c1[idx] += val;
idx = idx + lowbit(idx);
}
};
auto update_c2 = [&](int idx, int val) {//单点更新c2
while (idx <= n) {
c2[idx] += val;
idx = idx + lowbit(idx);
}
};
auto ask_c1 = [&](int x) {//询问 sum(b1,bx)
int sum = 0;
while (x) {
sum += c1[x];
x = x - lowbit(x);
}
return sum;
};
auto ask_c2 = [&](int idx) {//询问 sum(b1*1,bx*x)
int sum = 0;
while (idx) {
sum += c2[idx];
idx = idx - lowbit(idx);
}
return sum;
};
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
b[i] = a[i] - a[i - 1];
update_c1(i, b[i]);
update_c2(i, b[i] * i);
}
auto ope_range = [&](int l, int r, int x) {
update_c1(l, x), update_c1(r + 1, -x);
update_c2(l, x * l), update_c2(r + 1, -x * (r + 1));
};
auto ask_range = [&](int l, int r) {
return ask_c1(r) * (r + 1) - ask_c2(r) - ask_c1(l - 1) * l + ask_c2(l - 1);
};
while (m--) {
int x;
cin >> x;
if (x == 1) {
int l, r, k;
cin >> l >> r >> k;
ope_range(l, r, k);
} else {
int idx;
cin >> idx;
cout << ask_range(idx, idx) << endl;
}
}
}
权值数组
我们将在a数组中每个数出现的次数组成一个新的数组b,则b数组就是a数组的权值数组。比如a=[1,1,5,3,4],则b=[2,0,1,1,1]。我们需要注意一个问题,如果a[i]过大的话可能会出现爆掉的可能,所以需要考虑离散化。我们将在a数组中每个数出现的次数组成一个新的数组b,则b数组就是a数组的权值数组。比如a=[1,1,5,3,4],则b=[2,0,1,1,1]。我们需要注意一个问题,如果a[i]过大的话可能会出现爆掉的可能,所以需要考虑离散化。我们将在a数组中每个数出现的次数组成一个新的数组b,则b数组就是a数组的权值数组。比如a=[1,1,5,3,4],则b=[2,0,1,1,1]。我们需要注意一个问题,如果a[i]过大的话可能会出现爆掉的可能,所以需要考虑离散化。
单点修改+全局第k问题
我们考虑在权值数组上建立树状数组,那么当需要查询全局第k小的时候,我们只需要二分查询权值数组的前缀和,找到一个索引位置x满足∑i=1xbi<k并且∑i=1x+1bi>=k,那么此时x+1对应的数即为第k小的数。我们考虑在权值数组上建立树状数组,那么当需要查询全局第k小的时候,我们只需要二分查询权值数组的前缀和,找到一个索引位置x满足\sum_{i=1}^{x}b_i<k并且\sum_{i=1}^{x+1}b_i>=k,那么此时x+1对应的数即为第k小的数。我们考虑在权值数组上建立树状数组,那么当需要查询全局第k小的时候,我们只需要二分查询权值数组的前缀和,找到一个索引位置x满足∑i=1xbi<k并且∑i=1x+1bi>=k,那么此时x+1对应的数即为第k小的数。
单点修改的时候,我们只需要在val对应的索引位置+1或者−1即可。单点修改的时候,我们只需要在val对应的索引位置+1或者-1即可。单点修改的时候,我们只需要在val对应的索引位置+1或者−1即可。
map<int, int> a;//数值对索引
map<int, int> b;//索引对数值
vector<int> bit(MAXN, 0);
int index = 0;
int types = 0;
int n;
cin >> n;
vector<int> arr(n + 1);
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
auto update = [&](int idx, int val) {
while (idx < MAXN) {
bit[idx] += val;
idx = idx + lowbit(idx);
}
};
auto que = [&](int idx) {
int sum = 0;
while (idx > 0) {
sum += bit[idx];
idx = idx - lowbit(idx);
}
return sum;
};
auto insert = [&](int x) {
if (a.find(x) == a.end()) {
index++;
types++;
a[x] = index;
b[index] = x;
}
update(a[x], 1);
};
auto remove = [&](int val) {
if (a.find(val) != a.end()) {
int re = que(a[val]) - que(a[val] - 1);
if (re == 1) {
types--;
update(a[val], -1);
} else if (re != 0) {
update(a[val], -1);
} else {
cout << "已经删完了" << endl;
}
} else {
cout << "不存在" << endl;
}
};
auto que_k = [&](int k) {
int l = 1, r = index;
int ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (que(mid) >= k) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
if(ans==-1) {
cout<<"找不到"<<endl;
return -1ll;
}
return b[ans];
};
sort(arr.begin() + 1, arr.begin() + 1 + n);//因为要保证离散化前后的偏序关系,所以需要先排序。
for (int i = 1; i <= n; i++) {
insert(arr[i]);
}
cout << que_k(7) << endl;
remove(9);
cout << que_k(6) << endl;
remove(9);
//input:
//7
//1 2 7 8 9 4 5
//output:
//9
//8
//已经删完了
求逆序对
我们还可以在权值数组上建立树状数组来统计逆序对的数量。给定一个数组a,对于位置为i的数,需要找到有多少个j>i使得a[j]小于a[i]。考虑倒着遍历数组a,设dis[x]为x离散化后的值,对于a[i],我们只需要找到其后面比a[i]小的数。由于离散化保持偏序关系,那么我们可以直接查询(dis[a[i]]−1)的前缀和,即为与a[i]构成逆序对的数量,之后再让bit中dis[a[i]]加1。我们还可以在权值数组上建立树状数组来统计逆序对的数量。给定一个数组a,对于位置为i的数,需要找到有多少个j>i使得a[j]小于a[i]。考虑倒着遍历数组a,设dis[x]为x离散化后的值,对于a[i],我们只需要找到其后面比a[i]小的数。由于离散化保持偏序关系,那么我们可以直接查询(dis[a[i]]-1)的前缀和,即为与a[i]构成逆序对的数量,之后再让bit中dis[a[i]]加1。我们还可以在权值数组上建立树状数组来统计逆序对的数量。给定一个数组a,对于位置为i的数,需要找到有多少个j>i使得a[j]小于a[i]。考虑倒着遍历数组a,设dis[x]为x离散化后的值,对于a[i],我们只需要找到其后面比a[i]小的数。由于离散化保持偏序关系,那么我们可以直接查询(dis[a[i]]−1)的前缀和,即为与a[i]构成逆序对的数量,之后再让bit中dis[a[i]]加1。
map<int, int> a;//数值对索引
int index = 0;
int n;
cin >> n;
vector<int> bit(n+1, 0);
vector<int> arr(n + 1);
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
auto update = [&](int idx, int val) {
while (idx < n+1) {
bit[idx] += val;
idx = idx + lowbit(idx);
}
};
auto que = [&](int idx) {
int sum = 0;
while (idx > 0) {
sum += bit[idx];
idx = idx - lowbit(idx);
}
return sum;
};
auto temp=arr;
sort(arr.begin() + 1, arr.begin() + 1 + n);
for(int i=1;i<=n;i++){//离散化
if(a.find(arr[i])==a.end()){
index++;
a[arr[i]]=index;
}
}
int ans=0;
for(int i=n;i>=1;i--){
ans+=que(a[temp[i]]-1);
update(a[temp[i]],1);
}
cout<<ans<<endl;
例题
我们观察可以发现,题目要求可转化为求在子区间[l,r]中有多少个数比ac小。考虑当前样例:我们观察可以发现,题目要求可转化为求在子区间[l,r]中有多少个数比a_c小。考虑当前样例:我们观察可以发现,题目要求可转化为求在子区间[l,r]中有多少个数比ac小。考虑当前样例:
a[1,4,2,3,5],l=3,r=5,c=4。因为a4等于3,而在[3,5]中有一个2比3小,那么经过排序后原来4位置的数就会a[1,4,2,3,5],l=3,r=5,c=4。因为a_4等于3,而在[3,5]中有一个2比3小,那么经过排序后原来4位置的数就会a[1,4,2,3,5],l=3,r=5,c=4。因为a4等于3,而在[3,5]中有一个2比3小,那么经过排序后原来4位置的数就会
变到3+1=4的位置。变到3+1=4的位置。变到3+1=4的位置。
因为给定数组一定构成一个排列,那么我们在经过离散化后从小开始遍历并更新每个数,1−>1,2−>3,3−>4,4−>2,5−>5。因为给定数组一定构成一个排列,那么我们在经过离散化后从小开始遍历并更新每个数,1->1,2->3,3->4,4->2,5->5。因为给定数组一定构成一个排列,那么我们在经过离散化后从小开始遍历并更新每个数,1−>1,2−>3,3−>4,4−>2,5−>5。
for (int i = 1; i <= n; i++) cin >> arr[i], ys[arr[i]] = i;//记录一下每个数映射到的位置
我们从小开始查询,每次查询一个数,因为前面的数一定都是比他小的,比他小的数所在的映射被更新为1,我们只需要查询在[l,r]这个区间有多少个1就可以了。我们从小开始查询,每次查询一个数,因为前面的数一定都是比他小的,比他小的数所在的映射被更新为1,我们只需要查询在[l,r]这个区间有多少个1就可以了。我们从小开始查询,每次查询一个数,因为前面的数一定都是比他小的,比他小的数所在的映射被更新为1,我们只需要查询在[l,r]这个区间有多少个1就可以了。
typedef struct ask {
int l;
int r;
int ans;
int id;
} ask;
int n, m;
cin >> n >> m;
vector<int> ys(n + 1);
vector<int> arr(n + 1);
for (int i = 1; i <= n; i++) cin >> arr[i], ys[arr[i]] = i;
vector<vector<ask>> que(n + 1);
for (int i = 1; i <= m; i++) {
int l, r, x;
cin >> l >> r >> x;
que[arr[x]].push_back({l, r, 0, i});
}
vector<int> bit(100010, 0);
auto updt = [&](int x, int val) {
while (x <= n) {
bit[x] += val;
x += lowbit(x);
}
};
auto qu = [&](int idx) {
int sum = 0;
while (idx > 0) {
sum += bit[idx];
idx -= lowbit(idx);
}
return sum;
};
auto sum = [&](int l, int r) {
return qu(r) - qu(l - 1);
};
vector<ask> to;
for (int i = 1; i <= n; i++) {
if (que[i].size()) {
for (auto it: que[i]) {
it.ans = it.l + sum(it.l, it.r);
to.push_back(it);
}
}
updt(ys[i], 1);
}
sort(to.begin(), to.end(), [&](ask a, ask b) {
return a.id < b.id;
});
for (int i = 1; i <= m; i++) {
cout << to[i - 1].ans << endl;
}