前置知识
- 关于源码、反码和补码
1.二进制的最高位是符号位:0表示正数,1表示负数。
2.正数的原码、补码、反码都一样。
2.负数的反码=负数的原码符号位不变,其他位取反。
3.负数的补码=负数的反码+1,负数的反码=负数的补码-1。
4.在计算及运算中都是以补码的形式来进行计算的,不明白为什么可以试着用一个正数的补码和一个负数的补码进行运算(符带上号位)一下就知道了。
- 关于lowbit()函数
lowbit用于表示一个数的最低非零二进制符号位的大小。
eg:
6 => 00000110它的最低非零二进制为 10 表示二进制中的2。
12 => 00001100他的最低非零二进制为 100 表二进制中的4。
观察发现一个数 x 的最低非零二进制符号位的大小即为: x & -x即lowbit(x)=x & -x。
eg: 6 => 0 0 0 0 0 1 1 0(正数的补码)
-6 => 1 1 1 1 1 0 1 0 (负数的补码)
则6&-6 =0 0 0 0 0 0 1 0 = 2。
树状数组介绍
树状数组的最大优点是可以将数组的单点修改和区间求和的时间复杂度同时降低。
构建树状数组过程:假设一数组arr={1,2,3,4,5,6,7,8}对其进行以下处理:
将数组中每两个数的和加起来,在依次向上加,就得到了如上图的树状结构,这时如果我们要计算区间[3,7]的和只需用下图中橙色色部分减去蓝色部分即可:
仔细观察可以发现无论计算前几项的和都与上述数组中的偶数项没有关系,比如我们要计算前3项的和我们可以直将下图中的蓝色部分加起来,完全可以忽略掉橙色部分,而且这样做可以更高效的计算,再比如我们要计算前5项的和,可以直接将黄色部分加起来完全可以忽略掉绿色部分。
这样我们将上述数组中所有的偶数项都去掉便得到了一颗树,如下:
对这颗树进行中序遍历,便得到了我们想要的树状数组:bit={1,3,3,10,5,11,7,36}
可以观察到树状数组中每一层的区间长度为lowbit(i),其中i为数组的下标。并且树状数组的bit[i]的正上方的数正好就是bit[i + lowbit(i)]。
eg:第4层蓝色部分的下标i=5,那么对应的区间长度则为lowbit(5)=1;第3层蓝色部分的下标i=6,那么对应的区间长度则为lowbit(6)=2;第1层的蓝色部分的下表i=8,那么对应的区间长度则为lowbit(8)=8;
eg:第四层蓝色部分元素bit[5]正上方的元素为bit[5 + lowbit(5)]=bit[6];第三层蓝色部分元素bit[6]正上方元素为bit[6+lowbit(6)]=bit[8];
有了上述树状数组的性质我们就可以很方便的求出任意区间的和,但是疑问来了求任意区间的的和,利用前缀和不是更方便吗?这里就不得不提一下树状数组的单点修改了,如下图:
要修改下标为5的位置的元素,我们只需要修改蓝色区域的数就可以使这个数组继续保持树状数组的特性,时间复杂度为O(logn);而对于前缀和数组来说要修改某个位置的值并且还要保留前缀和数组的性质那就得修改对应下标的数以及后面的所有数,时间复杂度为O(n)。
树状数组例题
有些题的样例对java不太友好 ~.~。
例题1
P1168 中位数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import java.util.*;
public class Main {
static final int MAXN = 100010;
static int[] bit = new int[MAXN];
static int[] a = new int[MAXN];
static int[] b = new int[MAXN];
static int n, tot;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
for(int i = 1; i <= n; i++) {
a[i] = in.nextInt();
b[i] = a[i];
}
// 将 a 数组排序并去重,同时对 b 数组进行离散化
Arrays.sort(a, 1, n + 1);
tot = unique(a, 1, n); // 去重并获取新的总数
for(int i = 1; i <= n; i++) {
b[i] = Arrays.binarySearch(a, 1, tot + 1, b[i]);
}
for(int i = 1; i <= n; i++) {
add(b[i], 1); // 树状数组更新
if ((i & 1) == 1) {
System.out.println(a[findKth((i + 1) / 2)]);
}
}
}
static int lowbit(int x) {
return x & -x;
}
static void add(int pos, int x) {
for (int i = pos; i <= tot; i += lowbit(i)) {
bit[i] += x;
}
}
static int findKth(int k) {
int ans = 0, now = 0;
for (int i = 20; i >= 0; i--) {
ans += (1 << i);
if (ans > tot || now + bit[ans] >= k) ans -= (1 << i);
else now += bit[ans];
}
return ans + 1;
}
// 去重函数,同时返回新的数组大小
static int unique(int[] arr, int start, int end) {
int j = start; // 新数组的当前下标
for (int i = start + 1; i <= end; i++) {
if (arr[i] != arr[j]) {
arr[++j] = arr[i]; // 发现新元素,加入到新数组中
}
}
return j; // 返回新数组的大小
}
}
例题2
lP1438 无聊的数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import java.util.Scanner;
public class Main {
static long[] a, d, bit1, bit2;
static int n;
static int m;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
a = new long[n + 1];
d = new long[n + 1];
bit1 = new long[n + 1];
bit2 = new long[n + 1];
for(int i = 1; i <= n; i++) {
a[i] = scanner.nextLong();
d[i] = a[i] - a[i - 1];
add(i, d[i] - d[i - 1]);
}
for(int i = 1; i <= m; i++) {
int op = scanner.nextInt();
if(op == 1) {
long l = scanner.nextLong(), r = scanner.nextLong(), K = scanner.nextLong(), D = scanner.nextLong();
add(l, K);
add(l + 1, D - K);
add(r + 1, -(r - l + 1) * D - K);
add(r + 2, K + (r - l) * D);
} else {
long p = scanner.nextLong();
System.out.println(query(p));
}
}
scanner.close();
}
public static void add(long x, long delta) {
long id = x;
while(x <= n) {
bit1[(int)x] += delta;
bit2[(int)x] += delta * id;
x += lowBit(x);
}
}
public static long query(long x) {
long id = x, sum = 0;
while(x > 0) {
sum += (id + 1) * bit1[(int)x] - bit2[(int)x];
x -= lowBit(x);
}
return sum;
}
public static long lowBit(long x) {
return x & (-x);
}
}
例题3
P1438 无聊的数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import java.util.Scanner;
public class Main {
static long[] a, d, bit1, bit2;
static int n;
static int m;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
a = new long[n + 1];
d = new long[n + 1];
bit1 = new long[n + 1];
bit2 = new long[n + 1];
for(int i = 1; i <= n; i++) {
a[i] = scanner.nextLong();
d[i] = a[i] - a[i - 1];
add(i, d[i] - d[i - 1]);
}
for(int i = 1; i <= m; i++) {
int op = scanner.nextInt();
if(op == 1) {
long l = scanner.nextLong(), r = scanner.nextLong(), K = scanner.nextLong(), D = scanner.nextLong();
add(l, K);
add(l + 1, D - K);
add(r + 1, -(r - l + 1) * D - K);
add(r + 2, K + (r - l) * D);
} else {
long p = scanner.nextLong();
System.out.println(query(p));
}
}
scanner.close();
}
public static void add(long x, long delta) {
long id = x;
while(x <= n) {
bit1[(int)x] += delta;
bit2[(int)x] += delta * id;
x += lowBit(x);
}
}
public static long query(long x) {
long id = x, sum = 0;
while(x > 0) {
sum += (id + 1) * bit1[(int)x] - bit2[(int)x];
x -= lowBit(x);
}
return sum;
}
public static long lowBit(long x) {
return x & (-x);
}
}
例题4
P1774 最接近神的人 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int n;
static long ans;
static P[] p;
static int[] c;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
ans = 0;
p = new P[n+1];
c = new int[n+1];
for(int i=1; i<=n; ++i) {
p[i] = new P();
p[i].x = scanner.nextLong();
p[i].y = i;
}
Arrays.sort(p, 1, n+1); // 注意排序从1开始
for(int i=n; i>=1; i--) {
ans += query(p[i].y - 1);
update(p[i].y, 1);
}
System.out.println(ans);
scanner.close();
}
static void update(int k, int x) {
for(; k<=n; k += k&-k) c[k] += x;
}
static long query(int k) {
long ans = 0;
for(; k>0; k -= k&-k) ans += c[k];
return ans;
}
static class P implements Comparable<P> {
int y;
long x;
@Override
public int compareTo(P p2) {
return Long.compare(x, p2.x) == 0 ? Integer.compare(y, p2.y) : Long.compare(x, p2.x);
}
}
}
例题5
P4939 Agent2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import java.util.Scanner;
public class Main {
static int n, m;
static long[] c;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
c = new long[n + 1];
for (int i = 0; i < m; i++) {
int opt = sc.nextInt();
if (opt == 0) {
int x = sc.nextInt();
int y = sc.nextInt();
add(x, 1); // 单点增加1
add(y + 1, -1); // 差分,对区间的影响进行抵消
} else {
int x = sc.nextInt();
System.out.println(sum(x));
}
}
}
static int lowbit(int x) {
return x & (-x);
}
// 单点更新函数,根据树状数组的原理,更新相关节点的值
static void add(int x, int t) {
while (x <= n) {
c[x] += t;
x += lowbit(x);
}
}
// 前缀和函数,计算从1到x的所有元素的累积和
static long sum(int x) {
long ret = 0;
while (x > 0) {
ret += c[x];
x -= lowbit(x);
}
return ret;
}
}