可差分信息:如果知道部分的信息,用整体的信息和部分的信息就可以求出剩余部分的信息,那么这个信息是可差分的
P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
#include <vector>
using namespace std;
int n, m;
const int maxn = 500001;
int tree[maxn];
int lowbit(int i) {
return i & (-i);
}
void add(int i, int v) {//时间复杂度logn
while (i <= n) {
tree[i] += v;
i += lowbit(i);
}
}
int get_sum(int i) {//时间复杂度logn
int ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
int rangesum(int l,int r) {
return get_sum(r) - get_sum(l - 1);
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
add(i, a);
}
vector<int>ans;
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
if (a == 1)
add(b, c);
else
ans.push_back(rangesum(b, c));
}
for (auto i : ans)
cout << i << endl;
return 0;
}
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
维护其差分数组作为树状数组:这样范围增加相等于差分数组的单点增加;单点查询就可以用前缀和的方式求出
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 500002;
// 树状数组不维护原数组的信息,维护原数组的差分信息
// 注意下标一定从1开始,不从0开始
public static int[] tree = new int[MAXN];
public static int n, m;
public static int lowbit(int i) {
return i & -i;
}
public static void add(int i, int v) {
while (i <= n) {
tree[i] += v;
i += lowbit(i);
}
}
// 返回1~i范围累加和
public static int sum(int i) {
int ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int) in.nval;
in.nextToken();
m = (int) in.nval;
for (int i = 1, v; i <= n; i++) {
in.nextToken();
v = (int) in.nval;
add(i, v);
add(i + 1, -v);
}
for (int i = 1; i <= m; i++) {
in.nextToken();
int op = (int) in.nval;
if (op == 1) {
in.nextToken(); int l = (int) in.nval;
in.nextToken(); int r = (int) in.nval;
in.nextToken(); int v = (int) in.nval;
add(l, v);
add(r + 1, -v);
} else {
in.nextToken();
int index = (int) in.nval;
out.println(sum(index));
}
}
out.flush();
out.close();
br.close();
}
}
P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
A1到Ak的所有数值的累加和可以通过差分分解为下述的表达式
所以维护好两个信息
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 100001;
// 维护原始数组的差分信息:Di
public static long[] info1 = new long[MAXN];
// 维护原始数组的差分加工信息:(i-1) * Di
public static long[] info2 = new long[MAXN];
public static int n, m;
public static int lowbit(int i) {
return i & -i;
}
public static void add(long[] tree, int i, long v) {
while (i <= n) {
tree[i] += v;
i += lowbit(i);
}
}
public static long sum(long[] tree, int i) {
long ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
// 原始数组中[l..r]每个数值+v
public static void add(int l, int r, long v) {
add(info1, l, v);
add(info1, r + 1, -v);
add(info2, l, (l - 1) * v);
add(info2, r + 1, -(r * v));
}
// 原始数组中[l..r]范围上的累加和
public static long range(int l, int r) {
return sum(info1, r) * r - sum(info2, r) - sum(info1, l - 1) * (l - 1) + sum(info2, l - 1);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int) in.nval;
in.nextToken();
m = (int) in.nval;
long cur;
for (int i = 1; i <= n; i++) {
in.nextToken();
cur = (long) in.nval;
add(i, i, cur);
}
long v;
for (int i = 1, op, l, r; i <= m; i++) {
in.nextToken();
op = (int) in.nval;
if (op == 1) {
in.nextToken(); l = (int) in.nval;
in.nextToken(); r = (int) in.nval;
in.nextToken(); v = (long) in.nval;
add(l, r, v);
} else {
in.nextToken(); l = (int) in.nval;
in.nextToken(); r = (int) in.nval;
out.println(range(l, r));
}
}
out.flush();
out.close();
br.close();
}
}
308. 二维区域和检索 - 矩阵可修改 - 力扣(LeetCode)
public class Code04_TwoDimensionSingleAddIntervalQuery {
class NumMatrix {
public int[][] tree;
public int[][] nums;
public int n;
public int m;
// 入参二维数组下标从0开始
// 树状数组一定下标从1开始
public NumMatrix(int[][] matrix) {
n = matrix.length;
m = matrix[0].length;
tree = new int[n + 1][m + 1];
nums = new int[n + 1][m + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
update(i, j, matrix[i][j]);
}
}
}
private int lowbit(int i) {
return i & -i;
}
private void add(int x, int y, int v) {
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j += lowbit(j)) {
tree[i][j] += v;
}
}
}
// 从(1,1)到(x,y)这个部分的累加和
private int sum(int x, int y) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
ans += tree[i][j];
}
}
return ans;
}
// 实际二维数组的位置是(x,y)
// 树状数组上的位置是(x+1, y+1)
// 题目说的是单点更新,转化成单点增加(老值-新值)即可
// 不要忘了在nums中把老值改成新值
public void update(int x, int y, int v) {
add(x + 1, y + 1, v - nums[x + 1][y + 1]);
nums[x + 1][y + 1] = v;
}
// 实际二维数组的位置是(x,y)
// 树状数组上的位置是(x+1, y+1)
public int sumRegion(int a, int b, int c, int d) {
return sum(c + 1, d + 1) - sum(a, d + 1) - sum(c + 1, b) + sum(a, b);
}
}
}
P4514 上帝造题的七分钟 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
从(1,1)到(n,m)的和经过推到得到下述的关系式
#include <cstdio>
using namespace std;
const int MAXN = 2050;
const int MAXM = 2050;
int info1[MAXN][MAXM], info2[MAXN][MAXM], info3[MAXN][MAXM], info4[MAXN][MAXM];
int n, m;
int lowbit(int i) {
return i & -i;
}
void add(int x, int y, int v) {
int v1 = v;
int v2 = x * v;
int v3 = y * v;
int v4 = x * y * v;
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j += lowbit(j)) {
info1[i][j] += v1;
info2[i][j] += v2;
info3[i][j] += v3;
info4[i][j] += v4;
}
}
}
int sum(int x, int y) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
ans += (x + 1) * (y + 1) * info1[i][j] - (y + 1) * info2[i][j] - (x + 1) * info3[i][j] + info4[i][j];
}
}
return ans;
}
void add(int a, int b, int c, int d, int v) {
add(a, b, v);
add(c + 1, d + 1, v);
add(a, d + 1, -v);
add(c + 1, b, -v);
}
int range(int a, int b, int c, int d) {
return sum(c, d) - sum(a - 1, d) - sum(c, b - 1) + sum(a - 1, b - 1);
}
int main() {
char op;
int a, b, c, d, v;
scanf("%s", &op);
scanf("%d%d", &n, &m);
while (scanf("%s", &op) != EOF) {
if (op == 'X') {
scanf("%d%d", &n, &m);
} else if (op == 'L') {
scanf("%d%d%d%d%d", &a, &b, &c, &d, &v);
add(a, b, c, d, v);
} else {
scanf("%d%d%d%d", &a, &b, &c, &d);
printf("%d\n", range(a, b, c, d));
}
}
return 0;
}
例题:
P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 500001;
public static int[] arr = new int[MAXN];
public static int[] help = new int[MAXN];
public static int n;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int) in.nval;
for (int i = 1; i <= n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
out.println(compute());
out.flush();
out.close();
br.close();
}
public static long compute() {
return f(1, n);
}
// 归并分治
// 1) 统计i、j来自l~r范围的情况下,逆序对数量
// 2) 统计完成后,让arr[l...r]变成有序的
public static long f(int l, int r) {
if (l == r) {
return 0;
}
int m = (l + r) / 2;
return f(l, m) + f(m + 1, r) + merge(l, m, r);
}
public static long merge(int l, int m, int r) {
// i来自l.....m
// j来自m+1...r
// 统计有多少逆序对
long ans = 0;
for (int i = m, j = r; i >= l; i--) {
while (j >= m + 1 && arr[i] <= arr[j]) {
j--;
}
ans += j - m;
}
// 左右部分合并,整体变有序,归并排序的过程
int i = l;
int a = l;
int b = m + 1;
while (a <= m && b <= r) {
help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];
}
while (a <= m) {
help[i++] = arr[a++];
}
while (b <= r) {
help[i++] = arr[b++];
}
for (i = l; i <= r; i++) {
arr[i] = help[i];
}
return ans;
}
}
数组的离散化:如果直接按照原始数组数值的大小来决定tree数组的大小,会内存超限;所以需要数组离散化的技巧
1.首先将数组排序
2. 设置两个指针将不重复的数字集中放在数组的左边
3.二分搜索替换原始数组的值
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
public class Main {
public static int MAXN = 500001;
public static int[] arr = new int[MAXN];
public static int[] sort = new int[MAXN];
public static int[] tree = new int[MAXN];
public static int n, m;
public static int lowbit(int i) {
return i & -i;
}
public static void add(int i, int v) {
while (i <= m) {
tree[i] += v;
i += lowbit(i);
}
}
// 1~i范围的累加和
public static long sum(int i) {
long ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int) in.nval;
for (int i = 1; i <= n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
sort[i] = arr[i];
}
out.println(compute());
out.flush();
out.close();
br.close();
}
public static long compute() {
Arrays.sort(sort, 1, n + 1);
m = 1;
for (int i = 2; i <= n; i++) {
if (sort[m] != sort[i]) {
sort[++m] = sort[i];
}
}
for (int i = 1; i <= n; i++) {
arr[i] = rank(arr[i]);
}
long ans = 0;
for (int i = n; i >= 1; i--) {
// 右边有多少数字是 <= 当前数值 - 1
ans += sum(arr[i] - 1);
// 增加当前数字的词频
add(arr[i], 1);
}
return ans;
}
// 给定原始值v
// 返回排名值(排序部分1~m中的下标)
public static int rank(int v) {
int l = 1, r = m, mid;
int ans = 0;
while (l <= r) {
mid = (l + r) / 2;
if (sort[mid] >= v) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
return ans;
}
}
P1637 三元上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
求解三元组还需要在线知道每个点作左边二元组的数量和一元组的数量,并不断的更新,所以使用树状数组容易实现。此题因为数值范围较小所以不需要离散化也可以过
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 100001;
int tree1[maxn];
int tree2[maxn];
int n;
int lowbit(int i) {
return i & (-i);
}
void add(int* tree,int i, int v) {
while (i <= maxn) {
tree[i] += v;
i += lowbit(i);
}
}
long sum(int* tree, int i) {
long ans = 0;
while (i >= 1) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
int main() {
long ans = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
ans += sum(tree2, a - 1);
add(tree1, a, 1);
int rem=sum(tree1, a - 1);
add(tree2, a, rem);
}
cout << ans;
return 0;
}
673. 最长递增子序列的个数 - 力扣(LeetCode)
用树状数组维持一段以数字a到b结尾的最大递增子序列的长度和个数,所以此题中树状数组维持了一个不可差分信息。每遍历到一个数字,就查询小于这个数字的最大的长度和个数
public class Solution {
public static int MAXN = 2001;
public static int[] sort = new int[MAXN];
// 维护信息 : 以数值i结尾的最长递增子序列,长度是多少
// 维护的信息以树状数组组织
public static int[] treeMaxLen = new int[MAXN];
// 维护信息 : 以数值i结尾的最长递增子序列,个数是多少
// 维护的信息以树状数组组织
public static int[] treeMaxLenCnt = new int[MAXN];
public static int m;
public static int lowbit(int i) {
return i & -i;
}
// 查询结尾数值<=i的最长递增子序列的长度,赋值给maxLen
// 查询结尾数值<=i的最长递增子序列的个数,赋值给maxLenCnt
public static int maxLen, maxLenCnt;
public static void query(int i) {
maxLen = maxLenCnt = 0;
while (i > 0) {
if (maxLen == treeMaxLen[i]) {
maxLenCnt += treeMaxLenCnt[i];
} else if (maxLen < treeMaxLen[i]) {
maxLen = treeMaxLen[i];
maxLenCnt = treeMaxLenCnt[i];
}
i -= lowbit(i);
}
}
// 以数值i结尾的最长递增子序列,长度达到了len,个数增加了cnt
// 更新树状数组
public static void add(int i, int len, int cnt) {
while (i <= m) {
if (treeMaxLen[i] == len) {
treeMaxLenCnt[i] += cnt;
} else if (treeMaxLen[i] < len) {
treeMaxLen[i] = len;
treeMaxLenCnt[i] = cnt;
}
i += lowbit(i);
}
}
public static int findNumberOfLIS(int[] nums) {
int n = nums.length;
for (int i = 1; i <= n; i++) {
sort[i] = nums[i - 1];
}
Arrays.sort(sort, 1, n + 1);
m = 1;
for (int i = 2; i <= n; i++) {
if (sort[m] != sort[i]) {
sort[++m] = sort[i];
}
}
Arrays.fill(treeMaxLen, 1, m + 1, 0);
Arrays.fill(treeMaxLenCnt, 1, m + 1, 0);
int i;
for (int num : nums) {
i = rank(num);
query(i - 1);
if (maxLen == 0) {
// 如果查出数值<=i-1结尾的最长递增子序列长度为0
// 那么说明,以值i结尾的最长递增子序列长度就是1,计数增加1
add(i, 1, 1);
} else {
// 如果查出数值<=i-1结尾的最长递增子序列长度为maxLen != 0
// 那么说明,以值i结尾的最长递增子序列长度就是maxLen + 1,计数增加maxLenCnt
add(i, maxLen + 1, maxLenCnt);
}
}
query(m);
return maxLenCnt;
}
public static int rank(int v) {
int ans = 0;
int l = 1, r = m, mid;
while (l <= r) {
mid = (l + r) / 2;
if (sort[mid] >= v) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
return ans;
}
}
P1972 [SDOI2009] HH的项链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1.先将查询的操作按右边界排序
2.遍历贝壳,更新树状数组,并更新map中贝壳出现的最新位置,删去贝壳在树状数组旧的位置
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
public class Main {
public static int MAXN = 1000010;
public static int[] arr = new int[MAXN];
public static int[][] query = new int[MAXN][3];
public static int[] ans = new int[MAXN];
public static int[] map = new int[MAXN];
public static int[] tree = new int[MAXN];
public static int n, m;
public static int lowbit(int i) {
return i & -i;
}
public static void add(int i, int v) {
while (i <= n) {
tree[i] += v;
i += lowbit(i);
}
}
public static int sum(int i) {
int ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
public static int range(int l, int r) {
return sum(r) - sum(l - 1);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int) in.nval;
for (int i = 1; i <= n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
in.nextToken();
m = (int) in.nval;
for (int i = 1; i <= m; i++) {
in.nextToken();
query[i][0] = (int) in.nval;
in.nextToken();
query[i][1] = (int) in.nval;
query[i][2] = i;
}
compute();
for (int i = 1; i <= m; i++) {
out.println(ans[i]);
}
out.flush();
out.close();
br.close();
}
public static void compute() {
Arrays.sort(query, 1, m + 1, (a, b) -> a[1] - b[1]);
for (int s = 1, q = 1, l, r, i; q <= m; q++) {
r = query[q][1];
for (; s <= r; s++) {
int color = arr[s];
if (map[color] != 0) {
add(map[color], -1);
}
add(s, 1);
map[color] = s;
}
l = query[q][0];
i = query[q][2];
ans[i] = range(l, r);
}
}
}
2193. 得到回文串的最少操作次数 - 力扣(LeetCode)
1.贪心策略:先遇见的字符a,就先将距离其最远的相同的字符b放到和a对应的位置上,这样移动的步数是最少的
2.如果有一个数组a记录此时对应位置上的字符是否移走,移走了标为0,当遍历到某一位置时,其数组a对应位置之前的累加和就是要去的新位置,而9-i+1就是对应字符b要去的位置,并将数组b对应位置的值改为0,表示已经移走了,防止干扰后面的字符移动
3.怎么快速知道某个字符的最后一个所在的位置,end数组记录每中字符最后出现的位置;pre数组记录和每个位置上相同的字符之前出现的位置(和链式前向星类似)
void push(int v, int j) {
pre[j] = end[v];
end[v] = j;
}
int pop(int v) {
int ans = end[v];
end[v] = pre[end[v]];
return ans;
}
class Solution {
public:
static const int maxn = 2001;
int pre[maxn];
int end[26];
int tree[maxn];
int arr[maxn];
int help[maxn];
int n;
void push(int v, int j) {
pre[j] = end[v];
end[v] = j;
}
int pop(int v) {
int ans = end[v];
end[v] = pre[end[v]];
return ans;
}
int lowbit(int i) { return i & (-i); }
void add(int i, int v) {
while (i <= n) {
tree[i] += v;
i += lowbit(i);
}
}
void build() {
memset(end, 0, sizeof(end));
memset(tree, 0, sizeof(tree));
memset(arr, 0, sizeof(arr));
memset(help, 0, sizeof(help));
for (int i = 1; i <= n; i++)
add(i, 1);
}
int sum(int i) {
int ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
int merge(int l, int mid, int r) {
int ans = 0;
int sum = 0;
for (int i = l, j = mid + 1; i <= mid; i++) {
while (j <= r && arr[i] > arr[j]) {
sum++;
j++;
}
ans += sum;
}
int a = l, b = mid + 1, pos = l;
while (a <= mid && b <= r) {
help[pos++] = arr[a] < arr[b] ? arr[a++] : arr[b++];
}
while (a <= mid)
help[pos++] = arr[a++];
while (b <= r)
help[pos++] = arr[b++];
for (int i = l; i <= r; i++)
arr[i] = help[i];
return ans;
}
int number(int l, int r) {
if (l >= r)
return 0;
int mid = l + ((r - l) >> 1);
return number(l, mid) + number(mid + 1, r) + merge(l, mid, r);
}
int minMovesToMakePalindrome(string s) {
n = s.size();
build();
for (int i = 0, j = 1; i < n; i++, j++) {
push(s[i] - 'a', j);
}
for (int i = 0, l = 1, r, k; i < n; i++, l++) {
if (arr[l] == 0) {
r = pop(s[i] - 'a');
if (r > l) {
k = sum(l);
arr[l] = k;
arr[r] = n - k + 1;
} else {
arr[l] = (1 + n) / 2;
}
add(r, -1);
}
}
return number(1, n);
}
};