算 法
Warning本文发布于 2022/07/31,内容可能已过时。
小算法
ACM
快读快写
// 读整数ll read() {  ll x = 0; char c; bool f = 0;  while (c = getchar(), !isdigit(c))    if (c == '-') f = 1;  while (isdigit(c))    x = x * 10 + c - '0', c = getchar();  return f ? ~x + 1 : x;}void write(ll n) {  if (n < 0) putchar('-'), n = -n;  if (n > 9) write(n / 10);  putchar(n % 10 + '0');}头模板
#include <bits/stdc++.h>#define QAQ std#define endl "\n"#define ll long long#define vi vector<int>#define all(s) s.begin(), s.end()using namespace QAQ;#pragma GCC optimize(3)
const int MAX = 1e6;ll a[MAX + 5]{};
void solve() {
}
int main() {  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  ll T = 1;  cin >> T; //  while (T--) solve();  return 0;}复杂度
前缀和与差分
题目要求: 对长为 n 的数组进行 m 次查询:求 $[l, r]$ 之间的和
- 如果是暴力遍历,则最坏可达到 $O(n * m)$
 - 前缀和主要应用在对给定数组进行离线求和,与求区间和。离线在于预先遍历求一次和后,之后就可以直接读取了, 复杂度为 $O(n)$
 
此时,前 n 项和为:sum[n],$[l ,r]$ 之间的和为 sum[r] - sum[l - 1]
int sum(int l, int r) {  return sum[r] - sum[l - 1];}
for (int i = 1; i <= n; i++) {  cin >> a[i];  sum[i] = sum[i - 1] + a[i];}差分
如给数组 [l, r] 之间加上 c
const int len = 5;int b[len + 1]{}, // 差分数组  sum[len + 1]{}; // 差分的前缀和
// 区间加void add(int l, int r, int k) {  b[l] += k, b[r + 1] -= k;}
// 还原操作后的数组,及其前缀和void re() {  memset(arr, 0, len); memset(sum, 0, len);  for (int i = 1; i <= len; ++i) {    arr[i] = arr[i - 1] + b[i];    sum[i] = sum[i - 1] + arr[i];  }}
int main() {  for (int i = 1; i <= n; ++i){    cin >> arr[i];    b[i] = arr[i] - arr[i - 1];  }}树状数组
前缀和对于一次求和(n)多次查询很快,就 $O(n)$;但对于修改值后再查询却可能高达 $O(n^2)$,因为每次修改后还要再求前缀和
而树状数组对于修改和查询都是 $O(\log_2{n})$,因此整体就 $O(m\log_2{n})$
树状数组要做的就是,对数组进行单点或区间修改,并进行单点或区间查询和
树状数组的结构就是将完全二叉树用数组来表示
以数组 $Arr = {8,6,1,4,5,5,1,1,3,2,1,4,9,0,7,4}$ 为例


树状数组的几种变式
单点更新,区间查询
题目要求: 对数组进行 T 次操作,输入 3 个数字,分别表示:
1 x k含义:将第 $x$ 个数加上 $k$2 x y含义:输出区间 $[x,y]$ 内每个数的和
const int Max = 5e5 + 5;ll a[Max]{}, sum[Max]{};int n, T;
void add(int p, int x) {  while (p <= n) sum[p] += x, p += p & -p;}
int ask(int p) {  int res = 0;  while (p) res += sum[p], p -= p & -p;  return res;}int ask(int l, int r) {  return ask(r) - ask(l - 1);}
int main() {  cin >> n >> T;  for (int i = 1; i <= n; ++i) {    cin >> a[i]; add(i, a[i]);  }  while (T--) {    int t, l, r, x;    cin >> t >> l;    if (t == 1) {      cin >> x; add(l, x);    } else {      cin >> r;      cout << ask(l, r) << endl;    }  }}区间更新,区间查询
题目要求 变成了是在 $[l, r]$ 内增加 k,并求区间内的和
很难不想到利用 差分 来进行区间更新,那么:
const int Max = 5e5 + 5;ll a[Max]{}, sum1[Max]{}, sum2[Max]{};int n, T;
void add(int p, int k) {  for (int x = p; p <= n; p += p & -p) {    sum1[p] += k;    sum2[p] += k * (x - 1);  }}void add(int l, int r, int x) {  add(l, x), add(r + 1, -x);}int ask(int p) {  int ans = 0;  for (int x = p; p > 0; p -= p & -p) {    ans += x * sum1[p] - sum2[p];  }  return ans;}int ask(int l, int r) {  return ask(r) - ask(l - 1);}
int main() {  cin >> n >> T;  for (int i = 1; i <= n; ++i) {    cin >> a[i];    add(i, a[i] - a[i - 1]); // 差分  }  while (T--) {    int t, l, r, k;    cin >> t >> l >> r;    if (t == 1) {      cin >> k;      add(l, r, k);    } else {      cout << ask(l, r) << endl;    }  }  return 0;}