最近几天在学树状数组,做一个小总结!
树状数组
- 引入(应用)
- 给定一个序列,动态的修改某一个数、询问一段数字的和
- 一般算法:修改1次O(1),查询一次O(m),查询n次O(m*n)容易超时
模型
基本定义
- c[i]记录了多少个数的和
由图:
C1=a1
C2=a1+a2
C3=a3
C4=a1+a2+a3+a4
C5=a5
C6=a5+a6
……
C8=a1+a2+a3+a4+a5+a6+a7+a8
……
C[2^n]=a[1]+a[2]+….+a[2^n]
可知:
C[n]表示2^k个数的和(k为n对应的2进制数的末尾0的个数),其中2 ^ k = n & (n ^ n – 1) == n & -n (lowbit函数) - 如何修改单点
修改了某个a[i],就需改动所有包含a[i]的c[j],即修改从子节点到父节点路径上的所有c
父节点:
第X项的父(后继)节点是第X + (X & -X)项(由图可分析) - 如何求和
求和(前驱和)直接使用一次for循环,循环的次数为x的二进制表示中1的个数,即x前驱就是每次砍掉x二进制的最后一位1后的数
- c[i]记录了多少个数的和
各功能的伪代码
int lowbit(int x){//求和范围/
return x & (x ^ (x - 1));
}
void buildtree(){//建立c数组/
for (int i = 1; i <= n; ++i){
int l = lowbit(i);
for (int j = i - l + 1; j <= i; ++j) c[i] += a[j];
}
}
void add(int k, int d){//修改:给k加上d/
if (k > n) return ;
c[k] += d;
add(k + lowbit(k), d);
}
int sum(int s, int t){//求区间s~t的和/
int ans1 = 0, ans2 = 0;
for (int i = t; i >= 1; i -= lowbit(i)) ans1 += c[i];
for (int i = s - 1; i >= 1; i -= lowbit(i)) ans2 += c[i];
return ans1 - ans2;
}
基本树状数组的练习
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 100100
using namespace std;
int n, m;
int s, t;
int k, d;
string ch;
int a[MAXN], c[MAXN];
int lowbit(int x){
return x & (x ^ (x - 1));
}
void buildtree(){
for (int i = 1; i <= n; ++i){
int l = lowbit(i);
for (int j = i - l + 1; j <= i; ++j) c[i] += a[j];
}
}
void add(int k, int d){
if (k > n) return ;
c[k] += d;
add(k + lowbit(k), d);
}
int sum(int s, int t){
int ans1 = 0, ans2 = 0;
for (int i = t; i >= 1; i -= lowbit(i)) ans1 += c[i];
for (int i = s - 1; i >= 1; i -= lowbit(i)) ans2 += c[i];
return ans1 - ans2;
}
int main(){
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
buildtree();
scanf("%d", &m);
for (int i = 1; i <= m; ++i) {
cin >> ch;
if (ch == "ADD") {
scanf("%d %d", &k, &d);
add(k, d);
}
if (ch == "SUM") {
scanf("%d %d", &s, &t);
printf("%d\n", sum(s, t));
}
}
return 0;
}
异或求和问题不清楚的手完一下
#include <iostream>
#include <cstdio>
#include <cstdlib>
#define MAXN 100100
using namespace std;
int n, m;
int l, r;
int x, y;
bool bj;
int a[MAXN];
int c[MAXN];
int lowbit(int x){
return x & (x ^ (x - 1));
}
void buildtree(){
for (int i = 1; i <= n; ++i){
int l = lowbit(i);
for (int j = i - l + 1; j <= i; ++j) c[i] ^= a[j];
}
}
int sum(int l, int r){
int ans1 = 0, ans2 = 0;
for (int i = r; i >= 1; i -= lowbit(i)) ans1 ^= c[i];
for (int i = l - 1; i >= 1; i -= lowbit(i)) ans2 ^= c[i];
return ans1 ^ ans2;
}
void change(int x, int y){
int ch = sum(1, x) ^ sum(1, x - 1);
for (int i = x; i <= n; i += lowbit(i)) c[i] ^= ch, c[i] ^= y;
}
int main(){
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
buildtree();
for (int i = 1; i <= m; ++i) {
cin >> bj;
if (bj) {
scanf("%d %d", &l, &r);
printf("%d\n", sum(l, r));
}
if (!bj) {
scanf("%d %d", &x, &y);
change(x, y);
}
}
return 0;
}
这是一个重点
直接遍历遍数组,每个位置先add(a[i], 1),然后再查询sum(a[i]),即到第i位为止的逆序对个数为(i - sum(a[i]))
注意:c数组初始化均为0
//不用离散化/
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAXN 10000000
using namespace std;
int n;
int a[MAXN];
int c[MAXN];
int lowbit(int x) {
return x & (x ^ (x - 1));
}
void add(int x) {
if (x > 100000) return ;
c[x]++;
add(x + lowbit(x));
}
long long sum(int x) {
long long y = 0;
for (int i = x; i >= 1; i -= lowbit(i)) y += c[i];
return y;
}
int main(){
scanf("%d", &n);
long long ans = 0;
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), a[i]++;
for (long long i = 1; i <= n; ++i) {
add(a[i]);
ans += (i - sum(a[i]));
}
printf("%lld", ans);
return 0;
}
一道树状数组求逆序对的板子题
与上一题不能用离散化不同,此题数据量较大不用离散化会RE,放上离散化的代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define L 500000 + 10
using namespace std;
struct node{
int v, pos;
}a[L];
long long n, ans;
int num[L], c[L];
inline bool comp(node a, node b) {
return a.v < b.v;
}
inline int lowbit(int x) { return x & (-x);}
inline void add(int x) {
for (int i = x; i <= L; i += lowbit(i)) c[i]++;
}
inline long long sum(int x) {
long long s = 0;
for (int i = x; i; i -= lowbit(i)) s += c[i];
return s;
}
int main() {
while (scanf("%d", &n) && n != 0) {
memset(a, 0, sizeof(a));
memset(num, 0, sizeof(num));
memset(c, 0, sizeof(c));
ans = 0;
for (int i = 1; i <= n; ++i) scanf("%d", &a[i].v), a[i].pos = i;
sort(a + 1, a + 1 + n, comp);
for (int i = 1; i <= n; ++i) num[a[i].pos] = i;
for (int i = 1; i <= n; ++i) {
add(num[i]);
ans += (i - sum(num[i]));
}
printf("%lld\n", ans);
}
return 0;
}
这道题目的输入数据是多组的
注意可仅根据x坐标来设立c数组
对于每一次+1操作应该操作到最大值MAXN
注意数组的大小也应开到MAXN
一道树状数组基础题……
#include <iostream>
#include <cstdio>
#include <cstring>
#define L 20000 + 10
#define MAXN 32000 + 5
using namespace std;
int n, a, b;
int ans[MAXN], c[MAXN];
int lowbit(int x) {
return x & (-x);
}
void add(int x) {
while(x <= MAXN){
++c[x];
x += lowbit(x);
}
}
int query(int x) {
int q = 0;
while(x > 0){
q += c[x];
x -= lowbit(x);
}
return q;
}
int main() {
while (scanf("%d", &n) != EOF) {
memset(c, 0, sizeof(c));
memset(ans, 0, sizeof(ans));
for (int i = 1; i <= n; ++i) {
scanf("%d %d", &a, &b);
a++;
ans[query(a)]++;
add(a);
}
for (int i = 0; i < n; ++i)
printf("%d\n", ans[i]);
}
return 0;
}
总结未做题
简单:
POJ 1195 Mobile phones
中等
POJ 2155 Matrix
POJ 3321 Apple Tree
POJ 1990 MooFest
难题:
POJ 2464 Brownie Points II
——CYCKCN