模板庫



寫在前面

好困啊,不想寫題,來寫板子了。


基礎

快讀

inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) {
    if (ch == '-') {
      f = -1; 
    }
  }
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0'); 
  }
  return f * w;
}

隨機樹生成器

#include <bits/stdc++.h>
const int kTreeSize = 100000;
int main() {
  srand(time(0));
  int n = kTreeSize;
  printf("%d\n", n);
  for (int i = 2; i <= n; ++ i) {
    printf("%d %d\n", i, rand() % (i - 1) + 1);
  }
  return 0;
}

隨機圖生成器

保證無自環重邊。

#include <bits/stdc++.h>
#define pr std::pair
#define mp std::make_pair
const int kNodeSize = 10;
const int kEdgeSize = 20;
std::set <pr <int, int> > has;
int main() {
  srand(time(0));
  int n = kNodeSize, m = kEdgeSize;
  printf("%d %d\n", n, m);
  for (int i = 1; i <= m; ++ i) {
    int u_ = rand() % n + 1, v_ = rand() % n + 1;
    while (u_ == v_ || has.count(mp(u_, v_))) {
      u_ = rand() % n + 1, v_ = rand() % n + 1;
    }
    has.insert(mp(u_, v_));
    printf("%d %d\n", u_, v_);
  }
  return 0;
}

Checker

#include <bits/stdc++.h>
int main() {
  for (int i = 1; ; i ++) {
    printf("%d:", i);
    system("datamaker.exe");
    system("force.exe");
    system("std.exe");
    if (system("fc std.in force.in")) {
      break;
    }
  }
}

數據結構

並查集

將集合關系轉化為圖論中連通塊的方式進行維護。

P3367 【模板】並查集

路徑壓縮 + 按秩合並

均攤復雜度 \(O(m)\)

//知識點:並查集 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
//=============================================================
int n, m, fa[kMaxn], size[kMaxn]; 
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
int Find(int x_) {
  return x_ == fa[x_] ? x_ : fa[x_] = Find(fa[x_]);
}
void Union(int u_, int v_) {
  int fu = Find(u_), fv = Find(v_);
  if (fu == fv) return ;
  if (size[fu] > size[fv]) std::swap(fu, fv);
  fa[fu] = fv;
  size[fv] += size[fu];
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    fa[i] = i;
    size[i] = 1;
  }
  for (int i = 1; i <= m; ++ i) {
    int opt = read(), x = read(), y = read();
    if (opt == 1) {
      Union(x, y);
    } else {
      printf("%c\n", Find(x) == Find(y) ? 'Y' : 'N');
    }
  }
  return 0;
}

可撤銷並查集

記錄操作的變化,壓入棧中。
為保證回退次數,只能使用按秩合並。

//知識點:可撤銷並查集 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = 2e5 + 10;
//=============================================================
int n, m, top, fa[kMaxn], size[kMaxn]; 
struct Stack {
  int u, v, fa, size;
} st[kMaxm];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
int Find(int x_) {
  return x_ == fa[x_] ? x_ : fa[x_] = Find(fa[x_]);
}
void Union(int u_, int v_) {
  int fu = Find(u_), fv = Find(v_);
  if (size[fu] > size[fv]) std::swap(fu, fv);
  st[++ top] = (Stack) {fu, fv, fa[fu], size[fu]};
  if (fu != fv) {
    fa[fu] = fv;
    size[fv] += size[fu];
    size[fu] = 0;
  }
}
void Restore() {
  Stack now = st[top];
  if (now.u != now.v) {
    fa[now.u] = now.fa;
    size[now.v] -= now.size;
    size[now.u] = now.size;
  }
  top --;
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    fa[i] = i;
    size[i] = 1;
  }
  for (int i = 1; i <= m; ++ i) {
    int opt = read(), x = read(), y = read();
    if (opt == 1) {
      Union(x, y);
    } else {
      printf("%c\n", Find(x) == Find(y) ? 'Y' : 'N');
    }
  }
  return 0;
}

單調棧

求每個元素右側第一個比它大的數的位置。

維護一個單調遞減的棧。

P5788 【模板】單調棧

//知識點:單調棧 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 3e6 + 10;
//=============================================================
int n, top, a[kMaxn], st[kMaxn], ans[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  for (int i = 1; i <= n; ++ i) {
    while (top && a[st[top]] < a[i]) {
      ans[st[top]] = i;
      top --;
    }
    st[++ top] = i;
  }
  for (int i = 1; i <= n; ++ i) printf("%d ", ans[i]);
  return 0;
}

單調隊列

滑動窗口求區間最大值。

P1886 滑動窗口 /【模板】單調隊列

//知識點:單調隊列
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e6 + 10;
//=============================================================
int n, k, a[kMaxn];
int h, t, q[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Init() {
  h = t = 0;
}
void InsertMin(int pos_) {
  while (t >= h && a[q[t]] >= a[pos_]) {
    t --;
  }
  q[++ t] = pos_;
}
void InsertMax(int pos_) {
  while (t >= h && a[q[t]] <= a[pos_]) {
    t --;
  }
  q[++ t] = pos_;
}
int Query(int pos_) {
  while (t >= h && q[h] + k <= pos_) ++ h;
  return a[q[h]];
}
//=============================================================
int main() {
  n = read(), k = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  for (int i = 1; i < k; ++ i) InsertMin(i);
  for (int i = k; i <= n; ++ i) {
    InsertMin(i);
    printf("%d ", Query(i));
  }
  
  printf("\n");
  Init();
  for (int i = 1; i < k; ++ i) InsertMax(i);
  for (int i = k; i <= n; ++ i) {
    InsertMax(i);
    printf("%d ", Query(i));
  }
  return 0;
}

樹狀數組

二進制優化前綴和,維護前綴信息。

若想維護區間信息必須具有可減性。

單點加區間和

P3374 【模板】樹狀數組 1

//知識點:樹狀數組
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e6 + 10;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Bit {
  #define lowbit(x) (x&-x)
  int Lim;
  LL t[kMaxn];
  void Init(int lim_) {
    Lim = lim_;
  }
  void Insert(int pos_, LL val_) {
    for (int i = pos_; i <= Lim; i += lowbit(i)) {
      t[i] += val_;
    }
  }
  LL Sum(int pos_) {
    LL ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      ret += t[i];
    }
    return ret;
  }
  #undef lowbit
}
//=============================================================
int main() {
  n = read(), m = read();
  Bit::Init(n);
  for (int i = 1; i <= n; ++ i) {
    a[i] = read();
    Bit::Insert(i, a[i]); 
  }
  while (m --) {
    int opt = read();
    if (opt == 1) {
      int x = read(), k = read();
      Bit::Insert(x, 1ll * k);
    } else {
      int x = read(), y = read();
      printf("%lld\n", Bit::Sum(y) - Bit::Sum(x - 1));
    }
  }
  return 0;
}

ST 表

用長度為 2 的冪的區間覆蓋查詢區間。
維護不具有可減性信息。

靜態區間最大值。

P3865 【模板】ST表

//
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace ST {
  int Log2[kMaxn], f[kMaxn][22];
  void MakeST () {
    Log2[1] = 0;
    for (int i = 1; i <= n; ++ i) {
      f[i][0] = a[i];
      if (i > 1) Log2[i] = Log2[i >> 1] + 1;
    }
    
    for (int i = 1; i <= 22; ++ i) {
      for (int j = 1; j + (1 << i) - 1 <= n; ++ j) {
        f[j][i] = std::max(f[j][i - 1],
                           f[j + (1 << (i - 1))][i - 1]);
      }
    }
  }
  int Query(int l_, int r_) {
    int lth = Log2[r_ - l_ + 1];
    return std::max(f[l_][lth], f[r_ - (1 << lth) + 1][lth]);
  }
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  ST::MakeST();
  while (m --) {
    int l = read(), r = read();
    printf("%d\n", ST::Query(l, r));
  }
  return 0;
}

分塊

想了想還是把分塊扔到數據結構里了= =

分塊的基本思想是:通過對原數據的適當划分,並在划分后的每一個塊上預處理部分信息,從而較一般的暴力算法取得更優的時間復雜度。

Loj6280. 數列分塊入門 4

//知識點:分塊
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
const int kN = 5e4 + 10;
//=============================================================
int n, m;
int block_size, block_num, L[kN], R[kN], bel[kN];
LL a[kN], sum[kN], tag[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmin(LL &fir, LL sec) {
  if (sec < fir) fir = sec;
}
void PrepareBlock() {
  block_size = sqrt(n); //根據實際要求選擇一個合適的大小。
  block_num = n / block_size; 
  for (int i = 1; i <= block_num; ++ i) { //分配塊左右邊界。
    L[i] = (i - 1) * block_size + 1;
    R[i] = i * block_size;
  }
  if (R[block_num] < n) { //最后的一個較小的塊。
    ++ block_num;
    L[block_num] = R[block_num - 1] + 1;
    R[block_num] = n;
  }
  //分配元素所屬的塊編號。
  for (int i = 1; i <= block_num; ++ i) {
    for (int j = L[i]; j <= R[i]; ++ j) {
      bel[j] = i;
      sum[i] += a[j]; //預處理區間和
    }
  }
}
void Modify(int L_, int R_, int val_) {
  int bell = bel[L_], belr = bel[R_];
  if (bell == belr) {
    for (int i = L_; i <= R_; ++ i) {
      a[i] += val_;
      sum[bell] += val_;
    }
    return ;
  }
  for (int i = bell + 1; i <= belr - 1; ++ i) tag[i] += val_;
  for (int i = L_; i <= R[bell]; ++ i) {
    a[i] += val_;
    sum[bell] += val_;
  }
  for (int i = L[belr]; i <= R_; ++ i) {
    a[i] += val_;
    sum[belr] += val_;
  }
}
LL Query(int L_, int R_, int mod_) {
  int bell = bel[L_], belr = bel[R_], ret = 0;
  if (bell == belr) {
    for (int i = L_; i <= R_; ++ i) ret = (ret + a[i] + tag[bell]) % mod_;
    return ret;
  }
  for (int i = bell + 1; i <= belr - 1; ++ i) {
    ret = (ret + sum[i] + (R[i] - L[i] + 1) * tag[i] % mod_) % mod_;
  }
  for (int i = L_; i <= R[bell]; ++ i) ret = (ret + a[i] + tag[bell]) % mod_;
  for (int i = L[belr]; i <= R_; ++ i) ret = (ret + a[i] + tag[belr]) % mod_;
  return ret;
}
//=============================================================
int main() { 
  n = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  PrepareBlock();
  for (int i = 1; i <= n; ++ i) {
    int opt = read(), l = read(), r = read(), c = read();
    if (opt == 0) {
      Modify(l, r, c);
    } else {
      printf("%lld\n", Query(l, r, c + 1));
    }
  }
  return 0; 
}

線段樹

區間加區間和

P3372 【模板】線段樹 1

//知識點:線段樹 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  LL sum[kMaxn << 2], tag[kMaxn << 2];
  void Pushup(int now_) {
    sum[now_] = sum[ls] + sum[rs];
  }
  void Pushdown(int now_, int L_, int R_) {
    sum[ls] += 1ll * tag[now_] * (mid - L_ + 1);
    sum[rs] += 1ll * tag[now_] * (R_ - mid);
    tag[ls] += tag[now_];
    tag[rs] += tag[now_];
    tag[now_] = 0ll;
  }
  void Build(int now_, int L_, int R_) {
    if (L_ == R_) {
      sum[now_] = a[L_];
      tag[now_] = 0ll;
      return ;
    }
    Build(ls, L_, mid);
    Build(rs, mid + 1, R_);
    Pushup(now_); 
  }
  void Modify(int now_, int L_, int R_, int l_, int r_, LL val_) {
    if (l_ <= L_ and R_ <= r_) {
      sum[now_] += 1ll * (R_ - L_ + 1) * val_;
      tag[now_] += val_;
      return ;
    }
    Pushdown(now_, L_, R_);
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
    Pushup(now_);
  }
  LL Query(int now_, int L_, int R_, int l_, int r_) {
    if (l_ <= L_ and R_ <= r_) return sum[now_];
    Pushdown(now_, L_, R_);
    LL ret = 0;
    if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_);
    if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_);
    return ret;
  }
  #undef ls
  #undef rs
  #undef mid
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  Seg::Build(1, 1, n);
  while (m --) {
    int opt = read();
    if (opt == 1) {
      int l = read(), r = read(), k = read();
      Seg::Modify(1, 1, n, l, r, 1ll * k);
    } else {
      int l = read(), r = read();
      printf("%lld\n", Seg::Query(1, 1, n, l, r));
    }
  }
  return 0;
}

可持久化線段樹

P3919 【模板】可持久化線段樹 1(可持久化數組)

單點修改,單點歷史版本查詢。

//知識點:可持久化線段樹 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e6 + 10;
//=============================================================
int n, m, a[kMaxn], root[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Seg {
  #define ls (lson[now_])
  #define rs (rson[now_])
  #define mid ((L_+R_)>>1)
  int node_num, lson[kMaxn << 5], rson[kMaxn << 5], val[kMaxn << 5];
  void Build(int &now_, int L_, int R_) {
    now_ = ++ node_num;
    if (L_ == R_) {
      val[now_] = a[L_];
      return ;
    }
    Build(ls, L_, mid);
    Build(rs, mid + 1, R_);
  }
  void Modify(int &now_, int pre_, int L_, int R_, int pos_, int val_) {
    now_ = ++ node_num;
    if (L_ == R_) {
      val[now_] = val_;
      return ;
    }
    ls = lson[pre_];
    rs = rson[pre_];
    if (pos_ <= mid) Modify(ls, lson[pre_], L_, mid, pos_, val_);
    else Modify(rs, rson[pre_], mid + 1, R_, pos_, val_);
  }
  int Query(int now_, int L_, int R_, int pos_) {
    if (L_ == R_) return val[now_];
    if (pos_ <= mid) return Query(ls, L_, mid, pos_);
    return Query(rs, mid + 1, R_, pos_);
  }
  #undef ls
  #undef rs
  #undef mid
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  Seg::Build(root[0], 1, n);
  for (int i = 1; i <= m; ++ i) {
    int pre = read(), opt = read(), pos = read();
    if (opt == 1) {
      int val = read();
      Seg::Modify(root[i], root[pre], 1, n, pos, val);
    } else {
      printf("%d\n", Seg::Query(root[pre], 1, n, pos));
      root[i] = root[pre];
    }
  }
  return 0;
}

動態開點線段樹合並

動態開點 的線段樹的合並。
總復雜度是 \(O(n\log n)\)\(n\) 是節點個數。

P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合並

樹上路徑塗色,修改完后單點查詢最多塗色數。

//知識點:線段樹合並 
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define LL long long
const int kMaxn = 3e5 + 10;
const int kInf = 1e5 + 5;
//=============================================================
int n, m, root[kMaxn];
int edge_num, head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
int dep[kMaxn], fa[kMaxn], son[kMaxn], top[kMaxn], size[kMaxn];
int ans[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void AddEdge(int u_, int v_) {
  v[++ edge_num] = v_; 
  ne[edge_num] = head[u_];
  head[u_] = edge_num;
}
namespace Cut {
  void Dfs1(int u_, int fat_) {
    fa[u_] = fat_;
    size[u_] = 1;
    dep[u_] = dep[fat_] + 1;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fat_) continue ;
      Dfs1(v_, u_);
      size[u_] += size[v_];
      if (size[v_] > size[son[u_]]) son[u_] = v_;
    }
  }
  void Dfs2(int u_, int topn_) {
    top[u_] = topn_;
    if (son[u_]) Dfs2(son[u_], topn_);
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == son[u_] || v_ == fa[u_]) continue ;
      Dfs2(v_, v_);
    }
  }
  int GetLca(int x_, int y_) {
    for (; top[x_] != top[y_]; x_ = fa[top[x_]]) {
      if (dep[top[x_]] < dep[top[y_]]) std :: swap(x_, y_); 
    }
    return dep[x_] < dep[y_] ? x_ : y_;
  }
}

namespace Seg {
  #define ls (lson[now_])
  #define rs (rson[now_])
  #define mid ((L_+R_)>>1)
  int node_num, lson[kMaxn << 5], rson[kMaxn << 5], sum[kMaxn << 5];
  void Pushup(int now_) {
    sum[now_] = std::max(sum[ls], sum[rs]);
  }
  void Insert(int &now_, int L_, int R_, int pos_, int val_) {
    if (! now_) now_ = ++ node_num;
    if (L_ == R_) {
      sum[now_] += val_;
      return ;
    }
    if (pos_ <= mid) Insert(ls, L_, mid, pos_, val_);
    else Insert(rs, mid + 1, R_, pos_, val_);
    Pushup(now_);
  }
  //不新建結點寫法,now 直接連上了 y。
  //若之后 now 還和其他的合並,就破壞了 y 的結構。
  int Merge(int now_, int y_, int L_, int R_) {
    if (! now_ || ! y_) return now_ + y_;
    if (L_ == R_) {
      sum[now_] += sum[y_];
      return now_;
    }
    ls = Merge(ls, lson[y_], L_, mid);
    rs = Merge(rs, rson[y_], mid + 1, R_);
    Pushup(now_);
    return now_;
  }
  int Query(int now_, int L_, int R_) {
    if (L_ == R_) return L_;
    if (sum[ls] >= sum[rs]) return Query(ls, L_, mid);
    else return Query(rs, mid + 1, R_);
  }
  #undef ls
  #undef rs
  #undef mid
}
void Dfs(int u_) {
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa[u_]) continue ;
    Dfs(v_);
    root[u_] = Seg::Merge(root[u_], root[v_], 1, kInf);
  }
  if (! Seg::sum[root[u_]]) ans[u_] = 0;
  else ans[u_] = Seg::Query(root[u_], 1, kInf);
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i < n; ++ i) {
    int u = read(), v = read();
    AddEdge(u, v), AddEdge(v, u);
  }
  Cut::Dfs1(1, 0), Cut::Dfs2(1, 1); 
  for (int i = 1; i <= m; ++ i) {
    int u = read(), v = read(), z = read();
    int lca = Cut::GetLca(u, v);
    Seg::Insert(root[u], 1, kInf, z, 1);
    Seg::Insert(root[v], 1, kInf, z, 1);
    Seg::Insert(root[lca], 1, kInf, z, - 1);
    if (lca != 1) Seg::Insert(root[fa[lca]], 1, kInf, z, - 1);
  }
  Dfs(1);
  for (int i = 1; i <= n; ++ i) printf("%d\n", ans[i]);
  return 0;
}

主席樹

動態開點權值線段樹。

P3834 【模板】可持久化線段樹 2(主席樹)

靜態區間最 k 大。

「靜態區間第k小值」筆記

//知識點:主席樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 2e5 + 10;
//=============================================================
int n, m, d_num, a[kMaxn], data[kMaxn], map[kMaxn];
int root[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Prepare() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++ i) {
		a[i] = data[i] = read();
	}
	std::sort(data + 1, data + n + 1);
	data[0] = -0x3f3f3f3f;
	for (int i = 1; i <= n; ++ i) {
		if (data[i] != data[i - 1]) d_num ++;
		data[d_num] = data[i];
	}
	for (int i = 1; i <= n; ++ i) {
		int ori = a[i];
		a[i] = std :: lower_bound(data + 1, data + d_num + 1, ori) - data;
		map[a[i]] = ori;
	}
}
namespace Hjt {
  #define ls lson[now_]
  #define rs rson[now_]
  #define mid ((L_+R_)>>1)
  int node_num, lson[kMaxn << 5], rson[kMaxn << 5], size[kMaxn << 5];
  void Insert(int &now_, int pre_, int L_, int R_, int val_) {
    now_ = ++ node_num;
    size[now_] = size[pre_] + 1;
    ls = lson[pre_], rs = rson[pre_];
    if (L_ == R_) return ;
    if (val_ <= mid) Insert(ls, lson[pre_], L_, mid, val_);
    else Insert(rs, rson[pre_], mid + 1, R_, val_);
  }
  int Query(int r_, int l_, int L_, int R_, int k_) {
    if (L_ == R_) return L_;
    int sz = size[lson[r_]] - size[lson[l_]];
    if (k_ <= sz) return Query(lson[r_], lson[l_], L_, mid, k_);
    else return Query(rson[r_], rson[l_], mid + 1, R_, k_ - sz);
  }
  #undef ls
  #undef rs
  #undef mid
}
//=============================================================
int main() {
  Prepare();
	for (int i = 1; i <= n; ++ i) {
		Hjt::Insert(root[i], root[i - 1], 1, d_num, a[i]);	
	}
	for (int i = 1; i <= m; ++ i) {
		int l = read(), r = read(), k = read();
		printf("%d\n", map[Hjt::Query(root[r], root[l - 1], 1, d_num, k)]);
	}
	return 0;
}

帶修主席樹

P2617 Dynamic Rankings

樹狀數組套主席樹。

//知識點:主席樹 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
#define LL long long
const int kN = 1e6 + 10; 
//=============================================================
struct Opt {
  bool type;
  int l, r, k, pos, val;
} q[kN];
int n, m, d_num, data[kN], num[kN];
std::vector <int> tmp1, tmp2;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace Hjt {
  #define ls lson[now_]
  #define rs rson[now_]
  #define mid ((L_+R_)>>1)
  int node_num, root[kN], sz[kN << 5], lson[kN << 5], rson[kN << 5];
  void Modify(int &now_, int L_, int R_, int pos_, int val_) {
    if (!now_) now_ = ++ node_num;
    sz[now_] += val_;
    if (L_ == R_) return ;
    if (pos_ <= mid) Modify(ls, L_, mid, pos_, val_);
    else Modify(rs, mid + 1, R_, pos_, val_);
  }
  int Query(int L_, int R_, int k_) {
    if (L_ == R_) return L_;
    int sizel = 0;
    for (int i = 0; i < tmp1.size(); ++ i) sizel -= sz[lson[tmp1[i]]];
    for (int i = 0; i < tmp2.size(); ++ i) sizel += sz[lson[tmp2[i]]];
    
    if (k_ <= sizel) {
      for (int i = 0; i < tmp1.size(); ++ i) tmp1[i] = lson[tmp1[i]];
      for (int i = 0; i < tmp2.size(); ++ i) tmp2[i] = lson[tmp2[i]];
      return Query(L_, mid, k_);
    }
    for (int i = 0; i < tmp1.size(); ++ i) tmp1[i] = rson[tmp1[i]];
    for (int i = 0; i < tmp2.size(); ++ i) tmp2[i] = rson[tmp2[i]];
    return Query(mid + 1, R_, k_ - sizel);
  }
}
namespace Bit {
  #define low(x) (x&-x)
  void Add(int pos_, int val_) {
    int p = std::lower_bound(data + 1, data + d_num + 1, num[pos_]) - data;
    for (int i = pos_; i <= n; i += low(i)) {
      Hjt::Modify(Hjt::root[i], 1, d_num, p, val_);
    }
  }
  int Query(int l_, int r_, int k_) {
    tmp1.clear(), tmp2.clear();
    for (int i = l_ - 1; i; i -= low(i)) tmp1.push_back(Hjt::root[i]);
    for (int i = r_; i; i -= low(i)) tmp2.push_back(Hjt::root[i]);
    return Hjt::Query(1, d_num, k_);
  }
}
void Init() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) data[++ d_num] = num[i] = read();
  for (int i = 1; i <= m; ++ i) {
    char opt[5]; scanf("%s", opt + 1);
    q[i].type = (opt[1] == 'Q');
    if (q[i].type) {
      q[i].l = read(), q[i].r = read(), q[i].k = read();
    } else {
      q[i].pos = read(), data[++ d_num] = q[i].val = read();
    }
  }
  std::sort(data + 1, data + d_num + 1);
  d_num = std::unique(data + 1, data + d_num + 1) - data - 1;
  for (int i = 1; i <= n; ++ i) Bit::Add(i, 1);
}
//=============================================================
int main() {
  Init();
  for (int i = 1; i <= m; ++ i) {
    if (q[i].type) {
      int l = q[i].l, r = q[i].r, k = q[i].k;
      printf("%d\n", data[Bit::Query(l, r, k)]);
    } else {
      int pos = q[i].pos, val = q[i].val;
      Bit::Add(pos, -1);
      num[pos] = val;
      Bit::Add(pos, 1);
    }
  }
  return 0;
}

線段樹分治

有這樣的一種模型:

  • 給定一些僅在 給定時間范圍 內有效的操作。
  • 詢問某個時間點所有操作的貢獻。

考慮離線操作,對時間軸建立線段樹,將每個操作轉化為線段樹上的區間標記操作。
查詢時遍歷整棵線段樹,到達每個節點時執行相應的操作,到達葉節點統計貢獻,回溯時撤銷操作的影響

「離線可過」動態圖連通性

「筆記」線段樹分治

//知識點:線段樹分治,可撤銷並查集
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#define pr std::pair
#define mp std::make_pair
#define LL long long
const int kMaxn = 5e4 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
struct Operation {
  int type, u, v;
} opt[kMaxm << 1];
struct Stack {
  int u, v, fa, size;
} st[kMaxm];
int n, m, k, top, fa[kMaxn], size[kMaxn];
std::map <pr <int, int>, int> tim;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
int Find(int x_) {
  while (x_ != fa[x_]) x_ = fa[x_];
  return x_;
}
void Union(int u_, int v_) {
  int fu = Find(u_), fv = Find(v_);
  if (size[fu] > size[fv]) std :: swap(fu, fv);
  st[++ top] = (Stack) {fu, fv, fa[fu], size[fu]};
  if (fu != fv) {
    fa[fu] = fv;
    size[fv] += size[fu];
    size[fu] = 0;
  }
}
void Restore() {
  Stack now = st[top];
  if (now.u != now.v) {
    fa[now.u] = now.fa;
    size[now.v] -= now.size;
    size[now.u] = now.size;
  }
  top --;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  std::vector <int> t[kMaxm << 2];
  void Modify(int now_, int L_, int R_, int l_, int r_, int val_) {
    if (l_ <= L_ && R_ <= r_) {
      t[now_].push_back(val_);
      return ;
    }
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
  }
  void Solve(int now_, int L_, int R_) {
    for (int i = 0, lim = t[now_].size(); i < lim; ++ i) {
      Operation nowo = opt[t[now_][i]];
      Union(nowo.u, nowo.v);
    }
    if (L_ == R_) {
      if (opt[L_].type == 2) {
        int u = opt[L_].u, v = opt[L_].v;
        printf("%c\n", Find(u) == Find(v) ? 'Y' : 'N');
      }
      for (int i = 0, lim = t[now_].size(); i < lim; ++ i) {
        Restore();
      }
      return ;
    }
    Solve(ls, L_, mid);
    Solve(rs, mid + 1, R_);
    for (int i = 0, lim = t[now_].size(); i < lim; ++ i) {
      Restore();
    }
  }
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    int nowo = read(), u = read(), v = read();
    opt[i] = (Operation) {nowo, u, v};
    if (u > v) std::swap(u, v);
    if (nowo == 0) {
      tim[mp(u, v)] = i;
    } else if (nowo == 1) {
      Seg::Modify(1, 1, m, tim[mp(u, v)], i - 1, i);
      tim[mp(u, v)] = 0;
    } 
  }

  for (std::map <pr<int, int>, int>::iterator it = tim.begin(); it != tim.end(); ++ it) {
    if (it->second) {
      Seg::Modify(1, 1, m, it->second, m, it->second);
    }
  }
  for (int i = 1; i <= n; ++ i) {
    fa[i] = i;
    size[i] = 1;
  }
  Seg::Solve(1, 1, m);
  return 0;
}

輕重鏈剖分

將一棵樹分為多條重鏈,將樹信息轉化為序列信息。
維護路徑信息時用多條重鏈信息覆蓋路徑。

P3384 【模板】輕重鏈剖分

路徑加,路徑和,子樹加,子樹和,樹上路徑問題轉化為 dfn 序上的序列問題,線段樹維護序列信息。

我八十年沒寫樹剖了居然 1A 了= =

//知識點:重鏈剖分,線段樹 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, root, p, ori[kMaxn], a[kMaxn];
int e_num, head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
int fa[kMaxn], son[kMaxn], dep[kMaxn], size[kMaxn], top[kMaxn];
int d_num, dfn[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  LL sum[kMaxn << 2], tag[kMaxn << 2];
  void Pushup(int now_) {
    sum[now_] = (sum[ls] + sum[rs]) % p;
  }
  void Pushdown(int now_, int L_, int R_) {
    sum[ls] += 1ll * tag[now_] * (mid - L_ + 1) % p;
    sum[rs] += 1ll * tag[now_] * (R_ - mid) % p;
    tag[ls] += tag[now_] % p; 
    tag[rs] += tag[now_] % p;
    sum[ls] %= p, sum[rs] %= p;
    tag[ls] %= p, tag[rs] %= p;
    tag[now_] = 0ll;
  }
  void Build(int now_, int L_, int R_) {
    if (L_ == R_) {
      sum[now_] = a[L_] % p;
      tag[now_] = 0ll;
      return ;
    }
    Build(ls, L_, mid);
    Build(rs, mid + 1, R_);
    Pushup(now_); 
  }
  void Modify(int now_, int L_, int R_, int l_, int r_, LL val_) {
    if (l_ <= L_ and R_ <= r_) {
      sum[now_] += 1ll * (R_ - L_ + 1) * val_ % p;
      tag[now_] += val_ % p;
      sum[now_] %= p, tag[now_] %= p;
      return ;
    }
    Pushdown(now_, L_, R_);
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
    Pushup(now_);
  }
  LL Query(int now_, int L_, int R_, int l_, int r_) {
    if (l_ <= L_ and R_ <= r_) return sum[now_];
    Pushdown(now_, L_, R_);
    LL ret = 0;
    if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_);
    if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_);
    return ret % p;
  }
  #undef ls
  #undef rs
  #undef mid
}
namespace Cut {
  void Dfs1(int u_, int fa_) {
    fa[u_] = fa_;
    size[u_] = 1;
    dep[u_] = dep[fa_] + 1;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa_) continue ;
      Dfs1(v_, u_);
      if (size[v_] > size[son[u_]]) son[u_] = v_;
      size[u_] += size[v_];
    }
  }
  void Dfs2(int u_, int top_) {
    dfn[u_] = ++ d_num;
    a[d_num] = ori[u_];
    top[u_] = top_;
    if (son[u_]) Dfs2(son[u_], top_);
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa[u_] or v_ == son[u_]) continue ;
      Dfs2(v_, v_);
    }
  }
  void Modify(int u_, int v_, int val_) {
    for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
      if (dep[top[u_]] < dep[top[v_]]) {
        std::swap(u_, v_);
      }
      Seg::Modify(1, 1, n, dfn[top[u_]], dfn[u_], val_);
    }
    if (dep[u_] < dep[v_]) std::swap(u_, v_);
    Seg::Modify(1, 1, n, dfn[v_], dfn[u_], val_);
  }
  LL Query(int u_, int v_) {
    LL ret = 0;
    for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
      if (dep[top[u_]] < dep[top[v_]]) {
        std::swap(u_, v_);
      }
      ret += Seg::Query(1, 1, n, dfn[top[u_]], dfn[u_]);
      ret %= p;
    }
    if (dep[u_] < dep[v_]) std::swap(u_, v_);
    ret += Seg::Query(1, 1, n, dfn[v_], dfn[u_]);
    ret %= p;
    return ret;
  }
}
//=============================================================
int main() {
  n = read(), m = read(), root = read(), p = read();
  for (int i = 1; i <= n; ++ i) ori[i] = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_), AddEdge(v_, u_);
  }
  Cut::Dfs1(root, 0), Cut::Dfs2(root, root);
  Seg::Build(1, 1, n);
  while (m --) {
    int opt = read();
    if (opt == 1) {
      int u_ = read(), v_ = read(), val_ = read() % p;
      Cut::Modify(u_, v_, val_);
    } else if (opt == 2) {
      int u_ = read(), v_ = read();
      printf("%lld\n", Cut::Query(u_, v_));
    } else if (opt == 3) {
      int x_ = read(), val_ = read() % p;
      Seg::Modify(1, 1, n, dfn[x_], dfn[x_] + size[x_] - 1, val_);
    } else if (opt == 4) {
      int x_ = read();
      printf("%lld\n", Seg::Query(1, 1, n, dfn[x_], dfn[x_] + size[x_] - 1));
    }
  }
  return 0;
}

Splay

普通平衡樹

P3369 【模板】普通平衡樹

Splay 是一種二叉搜索樹的維護方式。它通過不斷將某節點旋轉到根節點,使得整棵樹仍滿足二叉搜索樹的性質,且保持平衡不至於退化為鏈,以保證復雜度。

//知識點:Splay
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace Splay {
  #define f fa[now_]
  #define ls son[now_][0]
  #define rs son[now_][1]
  const int kMaxNode = 1e6 + 10;
  int root, node_num, fa[kMaxNode], son[kMaxNode][2];
  int val[kMaxNode], cnt[kMaxNode], siz[kMaxNode];
  int top, bin[kMaxNode];
  void Pushup(int now_) { //更新節點大小信息
    if (!now_) return ;
    siz[now_] = cnt[now_];
    if (ls) siz[now_] += siz[ls];
    if (rs) siz[now_] += siz[rs];
  }
  int WhichSon(int now_) { //獲得兒子類型
    return now_ == son[f][1];
  }
  void Clear(int now_) { //清空節點,同時進行垃圾回收
    f = ls = rs = val[now_] = cnt[now_] = siz[now_] = 0;
    bin[++ top] = now_;
  }
  int NewNode(int fa_, int val_) { //建立新節點
    int now_ = top ? bin[top --] : ++ node_num; //優先調用垃圾堆
    f = fa_, val[now_] = val_, siz[now_] = cnt[now_] = 1;
    return now_;
  }
  void Rotate(int now_) { //旋轉操作
    int fa_ = f, whichson = WhichSon(now_);
    if (fa[f]) son[fa[f]][WhichSon(f)] = now_;
    f = fa[f];

    son[fa_][whichson] = son[now_][whichson ^ 1];
    fa[son[fa_][whichson]] = fa_;

    son[now_][whichson ^ 1] = fa_;
    fa[fa_] = now_;
    Pushup(fa_), Pushup(now_);
  }
  void Splay(int now_) { //旋轉到根操作
    for (; f != 0; Rotate(now_)) {
      if (fa[f]) Rotate(WhichSon(now_) == WhichSon(f) ? f : now_);
    }
    root = now_;
  }
  void Insert(int now_, int fa_, int val_) { //插入操作
    if (now_ && val[now_] != val_) {
      Insert(son[now_][val[now_] < val_], now_, val_);
      return ;
    }
    if (val[now_] == val_) ++ cnt[now_];
    if (!now_) {
      now_ = NewNode(fa_, val_);
      if (f) son[f][val[f] < val_] = now_;
    }
    Pushup(now_), Pushup(f), Splay(now_); //注意旋轉到根
    return ;
  }
  int Find(int now_, int val_) { //將權值 val 對應節點旋轉至根。在平衡樹上二分。
    if (!now_) return false; //不存在
    if (val_ < val[now_]) return Find(ls, val_);
    if (val_ == val[now_]) {
      Splay(now_);
      return true;
    }
    return Find(rs, val_);
  }
  void Delete(int val_) { //刪除一個權值 val 
    if (!Find(root, val_)) return ; //將 val 轉到根
    if (cnt[root] > 1) {
      -- cnt[root];
      Pushup(root);
      return ;
    }
    int oldroot = root;
    if (!son[root][0] && !son[root][1]) {
      root = 0;
    } else if (!son[root][0]) { 
      root = son[root][1], fa[root] = 0;
    } else if (!son[root][1]) {
      root = son[root][0], fa[root] = 0;
    } else if (son[root][0] && son[root][1]) {
      //將中序遍歷中 root 前的一個元素作為新的 root。該元素即為 root 左子樹中最大的元素。
      int leftmax = son[root][0];
      while (son[leftmax][1]) leftmax = son[leftmax][1];
      Splay(leftmax); //轉到根
      son[root][1] = son[oldroot][1], fa[son[root][1]] = root; //繼承信息
    }
    Clear(oldroot), Pushup(root);
  }
  int QueryRank(int val_) { //查詢 val_ 的排名
    Insert(root, 0, val_); //先插入,將其轉到根,查詢左子樹大小
    int ret = siz[son[root][0]] + 1;
    Delete(val_);
    return ret;
  }
  int QueryVal(int rk_) { //查詢排名為 rk 的權值。在平衡樹上二分。
    int now_ = root;
    while (true) {
      if (!now_) return -1;
      if (ls && siz[ls] >= rk_) { //注意 =
        now_ = ls;
      }else {
        rk_ -= ls ? siz[ls] : 0;
        if (rk_ <= cnt[now_]) { //該權值即為 val[now_]
          Splay(now_);
          return val[now_];
        }
        rk_ -= cnt[now_];
        now_ = rs;
      }
    }
  }
  int QueryPre(int val_) { //查詢前驅
    Insert(root, 0, val_); //插入 val_,將其旋轉到根,前驅(中序遍歷中前一個嚴肅)即為左子樹中最大的元素。
    int now_ = son[root][0];
    while (rs) now_ = rs;
    Delete(val_);
    return val[now_];
  }
  int QueryNext(int val_) { //查詢后繼
    Insert(root, 0, val_);
    int now_ = son[root][1];
    while (ls) now_ = ls;
    Delete(val_);
    return val[now_];
  }
}
//=============================================================
int main() { 
  int n = read();
  while (n --) {
    int opt = read(), x = read();
    if (opt == 1) {
      Splay::Insert(Splay::root, 0, x);
    } else if (opt == 2) {
      Splay::Delete(x);
    } else if (opt == 3) {
      printf("%d\n", Splay::QueryRank(x));
    } else if (opt == 4) {
      printf("%d\n", Splay::QueryVal(x));
    } else if (opt == 5) {
      printf("%d\n", Splay::QueryPre(x));
    } else if (opt == 6) {
      printf("%d\n", Splay::QueryNext(x));
    }
  }
  return 0; 
}

文藝平衡樹

P3391 【模板】文藝平衡樹

將被操作區間放置到一棵單獨的子樹內,對整棵子樹打標記實現區間修改。注意標記下放的寫法,反轉標記取反並交換左右兒子。

//知識點:Splay
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace Splay {
  #define f fa[now_]
  #define ls son[now_][0]
  #define rs son[now_][1]
  const int kMaxNode = kN;
  int root, node_num, fa[kMaxNode], son[kMaxNode][2];
  int val[kMaxNode], siz[kMaxNode], cnt[kMaxNode];
  bool tag[kN];
  int top, bin[kMaxNode];
  void Pushup(int now_) {
    if (!now_) return ;
    siz[now_] = cnt[now_];
    if (ls) siz[now_] += siz[ls];
    if (rs) siz[now_] += siz[rs];
  }
  void Pushdown(int now_) {
    if (!now_ || !tag[now_]) return ;
    tag[ls] ^= 1, tag[rs] ^= 1;
    std::swap(ls, rs);
    tag[now_] = false;
  }
  void Clear(int now_) {
    f = ls = rs = val[now_] = cnt[now_] = siz[now_] = 0;
    bin[++ top] = now_;
  }
  int NewNode(int fa_, int val_) {
    int now_ = top ? bin[top --] : ++ node_num;
    f = fa_, val[now_] = val_, cnt[now_] = siz[now_] = 1;
    return now_;
  }
  bool WhichSon(int now_) {
    return now_ == son[f][1];
  }
  void Rotate(int now_) {
    int fa_ = f, whichson = WhichSon(now_);
    if (fa[f]) son[fa[f]][WhichSon(f)] = now_;
    f = fa[f];

    son[fa_][whichson] = son[now_][whichson ^ 1];
    fa[son[fa_][whichson]] = fa_;

    son[now_][whichson ^ 1] = fa_;
    fa[fa_] = now_;
    Pushup(fa_), Pushup(now_);
  }
  void Splay(int now_, int fa_) {
    for (; f != fa_; Rotate(now_)) {
      if (fa[f] != fa_) Rotate(WhichSon(f) == WhichSon(now_) ? f : now_); 
    }
    if (!fa_) root = now_;
  }
  void Build(int &now_, int fa_, int L_, int R_) {
    if (L_ > R_) return;
    int mid = (L_ + R_) >> 1; 
    now_ = NewNode(fa_, mid);
    Build(ls, now_, L_, mid - 1);
    Build(rs, now_, mid + 1, R_);
    Pushup(now_);
  }
  int Kth(int now_, int rk_) {
    Pushdown(now_);
    if (ls && rk_ <= siz[ls]) return Kth(ls, rk_);
    if (rk_ <= siz[ls] + cnt[now_]) return now_;
    return Kth(rs, rk_ - siz[ls] - cnt[now_]);
  }
  void Modify(int L_, int R_) {
    int x = Kth(root, L_ - 1 + 1), y = Kth(root, R_ + 1 + 1); //偏移量
    Splay(x, 0), Splay(y, x);
    tag[son[son[root][1]][0]] ^= 1;
  }
  void Print(int now_) {
    Pushdown(now_);
    if (ls) Print(ls);
    if (val[now_] && val[now_] <= n) printf("%d ", val[now_]);
    if (rs) Print(rs);
  }
}
//=============================================================
int main() { 
  n = read(), m = read();
  Splay::Build(Splay::root, 0, 0, n + 1);
  while (m --) {
    int l = read(), r = read();
    Splay::Modify(l ,r);
  }
  Splay::Print(Splay::root);
  return 0; 
}

用於解決動態樹問題,通過 Splay 動態維護樹鏈剖分結構,從而將樹上問題轉化為 Splay 上的序列問題的一種數據結構。

支持動態加刪除邊,修改路徑信息,查詢路徑、子樹信息。不支持修改子樹。

P1501 Tree II

//知識點:LCT
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
const LL mod = 51061;
//=============================================================
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace LCT {
  #define f fa[now_]
  #define ls son[now_][0]
  #define rs son[now_][1]
  const int kMaxNode = kN;
  int fa[kMaxNode], son[kMaxNode][2], val[kMaxNode]; //Splay 的結構信息
  LL siz[kMaxNode], sum[kMaxNode], tagplus[kMaxNode], tagprod[kMaxNode]; //子樹大小,子樹和,兩種標記
  bool tagrev[kMaxNode]; //Splay 的子樹反轉標記
  void Pushup(int now_) { //維護子樹和 和 子樹大小
    sum[now_] = (sum[ls] + sum[rs] + val[now_]) % mod;
    siz[now_] = siz[ls] + siz[rs] + 1;
  }
  void PushReverse(int now_) { //子樹反轉標記,使得 Splay 節點的中序遍歷反向。若原 splay 表示一條自頂向下的鏈,反轉相當於將 splay 表示的鏈的邊反向,父子關系互換。
    if (!now_) return;
    std::swap(ls, rs);
    tagrev[now_] ^= 1;
  }
  void PushPlus(int now_, LL val_) {
    if (!now_) return;
    val[now_] = (val[now_] + val_) % mod;
    sum[now_] = (sum[now_] + siz[now_] * val_ % mod) % mod;
    tagplus[now_] = (tagplus[now_] + val_) % mod;
  }
  void PushProd(int now_, LL val_) {
    if (!now_) return;
    val[now_] = val[now_] * val_ % mod;
    sum[now_] = sum[now_] * val_ % mod;
    tagplus[now_] = tagplus[now_] * val_ % mod;
    tagprod[now_] = tagprod[now_] * val_ % mod;
  }
  void Pushdown(int now_) { //注意下放順序
    LL plus = tagplus[now_], prod = tagprod[now_], rev = tagrev[now_];
    if (prod != 1) PushProd(ls, prod), PushProd(rs, prod);
    if (plus) PushPlus(ls, plus), PushPlus(rs, plus);
    if (rev) PushReverse(ls), PushReverse(rs);
    tagprod[now_] = 1, tagplus[now_] = 0, tagrev[now_] = 0;
  }
  bool IsRoot(int now_) { //判斷 now_ 是否為當前 Splay 的根
    return son[f][0] != now_ && son[f][1] != now_;
  }
  bool WhichSon(int now_) {
    return son[f][1] == now_;
  }
  void Rotate(int now_) {
    int fa_ = f, w = WhichSon(now_);
    if (!IsRoot(f)) son[fa[f]][WhichSon(f)] = now_;
    f = fa[f];

    son[fa_][w] = son[now_][w ^ 1];
    fa[son[fa_][w]] = fa_;

    son[now_][w ^ 1] = fa_;
    fa[fa_] = now_;
    Pushup(fa_), Pushup(now_); 
  }
  void Update(int now_) { //將 Splay 路徑上的所有標記下放
    if (!IsRoot(now_)) Update(f);
    Pushdown(now_);
  }
  void Splay(int now_) {
    Update(now_);
    for (; !IsRoot(now_); Rotate(now_)) {
      if (!IsRoot(f)) Rotate(WhichSon(f) == WhichSon(now_) ? f : now_);
    }
  }
  void Access(int now_) { //使得樹中由根->now 的鏈成為實鏈,構造出由它們組成的 Splay,滿足 Splay 的中序遍歷深度遞減的性質。
    //自下向上構建,舍棄父親的原有右兒子,換成 -> last 的鏈。
    for (int last_ = 0; now_; last_ = now_, now_ = f) {
      Splay(now_), rs = last_;
      Pushup(now_);
    }
  }
  void MakeRoot(int now_) { //使 now 成為原樹的根
    Access(now_); //先使得樹中由根->now 的鏈成為實鏈,構造出由它們組成的 Splay。
    Splay(now_); //使 now 成為 splay 的根節點
    PushReverse(now_); //將根->now 的鏈反轉,使 now 成為原樹的根。原理參考 PushReverse 函數的注釋。
  }
  int Find(int now_) { //找到 now_ 所在原樹的根
    Access(now_); 
    Splay(now_);
    while (ls) Pushdown(now_), now_ = ls; //使得樹中由根->now 的鏈成為實鏈,構造出由它們組成的 Splay,再找到 Splay 中序遍歷的第一個元素,即為原樹的根。
    
    Splay(now_); //為了下一步操作,把根再轉回去
    return now_; 
  }
  void Split(int x_, int y_) { //構造由路徑 x->y 組成的 Splay
    MakeRoot(x_); //使 x 成為根,構造出根 -> y 的 Splay 即得,Splay 根的子樹信息即為路徑信息。
    Access(y_);
    Splay(y_);
  }
  void Link(int x_, int y_) { //加邊 (x,y)
    MakeRoot(x_); //使 x 成為根,再給根一個父親
    if (Find(y_) != x_) fa[x_] = y_;
  }
  void Cut(int x_, int y_) { //刪邊 (x,y)
    MakeRoot(x_); //使 x 成為根
    //Find(y_) != x_ 保證 y 與 x 連通
    //在 Find 函數中 Access(y) 之后,y 所在的 splay 由 x->y 的鏈構成。又 x 是 splay 的根,y 的中序遍歷在 x 后,則若邊 (x,y) 存在,則 y 的位置只有一種可能:  
    //y 是 x 的右兒子,且 y 沒有左兒子。從而保證 y 在中序遍歷中是 x 的后一個。
    if (Find(y_) != x_ || fa[y_] != x_ || son[y_][0]) return ;
    fa[y_] = son[x_][1] = 0; //斷絕父子關系
    Pushup(x_);
  }
  void Modify(int x_, int y_, int val_, int type) { //路徑修改
    Split(x_, y_); //給路徑構成的 Splay 打標記
    if (!type) PushPlus(y_, val_);
    else PushProd(y_, val_);
  }
  LL Query(int x_, int y_) { //路徑查詢
    Split(x_, y_); //返回路徑構成的 Splay 的信息
    return sum[y_];
  }
}
//=============================================================
signed main() { 
  int n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    LCT::val[i] = LCT::tagprod[i] = LCT::siz[i] = 1;
  }
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    LCT::Link(u_, v_);
  }
  for(int i = 1; i <= m; i ++) {
	  char opt[2]; scanf("%s", opt);
	  int u1 = read(), v1 = read();
	  if(opt[0] == '+') LCT::Modify(u1, v1, read(), 0);
	  if(opt[0] == '*') LCT::Modify(u1, v1, read(), 1);
	  if(opt[0] == '/') printf("%lld\n", LCT::Query(u1, v1));
    if(opt[0] == '-') {
      int u2 = read(), v2 = read(); 
      LCT::Cut(u1, v1), LCT::Link(u2, v2);
    }
	}
  return 0; 

圖論

多源最短路

Floyd

//知識點:多源最短路
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e3 + 10;
//=============================================================
int n, m, s, f[kMaxn][kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Floyd() {
  for (int i = 1; i <= n; ++ i) f[i][i] = 0;
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      for (int k = 1; k <= n; ++ k) {
        Chkmin(f[j][k], f[j][i] + f[i][k]);
      }
    }
  }
}
//=============================================================
int main() {
  n = read(), m = read(), s = read();
  memset(f, 63, sizeof (f));
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    Chkmin(f[u_][v_], w_);
  }
  Floyd();
  for (int i = 1; i <= n; ++ i) {
    printf("%d ", f[s][i]);
//    printf("%d ", f[s][i] >= 0x3f3f3f3f ? 2147483647 : f[s][i]);
  }
  return 0;
}

單源最短路三合一

Dijkstra,Bellman-Ford,Spfa 三合一代碼。

P3371 【模板】單源最短路徑(弱化版)

//知識點:單源最短路 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 1e5 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
int n, m, s;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Dijkstra(int s_) {
  std::priority_queue <pr <int, int> > q;
  memset(dis, 63, sizeof (dis));
  memset(vis, 0, sizeof (vis));
  dis[s_] = 0;
  q.push(mp(0, s_));
  
  while (! q.empty()) {
    int u_ = q.top().second;
    q.pop();
    if (vis[u_]) continue ;
    vis[u_] = true;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u_] + w_ < dis[v_]) {
        dis[v_] = dis[u_] + w_;
        q.push(mp(-dis[v_], v_));
      }
    }
  }
}
void BellmanFord(int s_) {
  memset(dis, 63, sizeof (dis));
  dis[s_] = 0;
  while (1) {
    bool flag = true;
    for (int u_ = 1; u_ <= n; ++ u_) {
      for (int i = head[u_]; i; i = ne[i]) {
        int v_ = v[i], w_ = w[i];
        if (dis[u_] + w_ < dis[v_]) {
          dis[v_] = dis[u_] + w_;
          flag = false;
        } 
      }
    }
    if (flag) break ;
  }
}
void Spfa(int s_) {
  std::queue <int> q;
  memset(vis, 0, sizeof (vis));
  memset(dis, 63, sizeof (dis));
  q.push(s_);
  dis[s_] = 0;
  vis[s_] = true;
  
  while (! q.empty()) {
    int u_ = q.front();
    q.pop();
    vis[u_] = false;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u_] + w_ < dis[v_]) {
        dis[v_] = dis[u_] + w_;
        if (! vis[v_]) {
          q.push(v_);
          vis[v_] = true;
        }
      }
    }
  }
}
//=============================================================
int main() {
  n = read(), m = read(), s = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_);
  }
  Dijkstra(s);
  // BellmanFord(s);
  // Spfa(s);
  for (int i = 1; i <= n; ++ i) {
    printf("%d ", dis[i]);
//    printf("%d ", dis[i] >= 0x3f3f3f3f ? 2147483647 : dis[i]);
  }
  return 0;
}

最小生成樹

P3366 【模板】最小生成樹

Kruscal

克魯斯卡爾大戰克蘇魯。

基於排序與並查集的貪心加邊。

//知識點:最小生成樹 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kMaxn = 5e4 + 10;
const int kMaxm = 4e5 + 10;
//=============================================================
int n, m, ans, fa[kMaxn], size[kMaxn];
struct Edge {
  int u, v, w;
} e[kMaxm];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool CompareEdge(Edge f_, Edge s_) {
  return f_.w < s_.w;
}
int Find(int x_) {
  return x_ == fa[x_] ? x_ : fa[x_] = Find(fa[x_]);
}
void Union(int u_, int v_) {
  int fu = Find(u_), fv = Find(v_);
  if (fu == fv) return ;
  if (size[fu] > size[fv]) std::swap(fu, fv);
  fa[fu] = fv;
  size[fv] += size[fu];
}
void Kruscal() {
  int tot = 0;
  std::sort(e + 1, e + m + 1, CompareEdge);
  for (int i = 1; i <= n; ++ i) {
    fa[i] = i;
    size[i] = 1; 
  }
  for (int i = 1; i <= m && tot < n - 1; ++ i) {
    int u_ = e[i].u, v_ = e[i].v, w_ = e[i].w;
    if (Find(u_) == Find(v_)) continue ;
    Union(u_, v_);
    ans += w_;
    ++ tot;
  }
  if (tot < n - 1) ans = -1;
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    e[i] = (Edge) {u_, v_, w_};
  }
  Kruscal();
  if (ans < 0) {
    printf("orz\n");
  } else {
    printf("%d\n", ans);
  }
  return 0;
}

Prim

類似 Dijkstra,貪心地找將每一個點加入連通塊的邊的最小值。

用了堆優化。

//知識點:單元最短路 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 5e4 + 10;
const int kMaxm = 4e5 + 10;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Prim() {
  std::priority_queue <pr <int, int> > q;
  memset(dis, 63, sizeof (dis));
  memset(vis, 0, sizeof (vis));
  dis[1] = 0;
  q.push(mp(0, 1));
  
  int tot = 0;
  while (! q.empty() && tot < n) {
    int u_ = q.top().second;
    q.pop();
    if (vis[u_]) continue ;
    ++ tot;
    vis[u_] = true;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (w_ < dis[v_] && !vis[v_]) {
        dis[v_] = w_;
        q.push(mp(-dis[v_], v_));
      }
    }
  }
  if (tot < n) ans = -1;
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_);
    AddEdge(v_, u_, w_);
  }
  Prim();
  if (ans < 0) {
    printf("orz\n");
  } else {
    for (int i = 1; i <= n; ++ i) ans += dis[i];
    printf("%d\n", ans);
  }
  
  return 0;
}

最近公共祖先

P3379 【模板】最近公共祖先(LCA)

倍增

優化暴力上跳,用2 的冪的和拼出跳的深度。

//知識點:最近公共祖先 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 5e5 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, root;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int dep[kMaxn], fa[kMaxn][21];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Dfs(int u_, int fa_) {
  fa[u_][0] = fa_;
  dep[u_] = dep[fa_] + 1;
  for (int i = 1; i <= 20; ++ i) {
    fa[u_][i] = fa[fa[u_][i - 1]][i - 1];
  }
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue ;
    Dfs(v_, u_);
  }
}
int Lca(int u_, int v_) {
  if (dep[u_] < dep[v_]) std::swap(u_, v_);
  for (int i = 20; i >= 0; -- i) {
    if (dep[fa[u_][i]] >= dep[v_] ) {
      u_ = fa[u_][i];
    }
  }
  if (u_ == v_) return u_;
  for (int i = 20; i >= 0; -- i) {
    if (fa[u_][i] != fa[v_][i]) {
      u_ = fa[u_][i];
      v_ = fa[v_][i];
    }
  }
  return fa[u_][0];
}
//=============================================================
int main() {
  n = read(), m = read(), root = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_);
    AddEdge(v_, u_);
  }
  Dfs(root, 0);
  while (m --) {
    int u_ = read(), v_ = read();
    printf("%d\n", Lca(u_, v_));
  }
  return 0;
}

重鏈剖分

暴力上跳轉化為跳重鏈。

//知識點:重鏈剖分
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 5e5 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, root;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int fa[kMaxn], son[kMaxn], dep[kMaxn], size[kMaxn], top[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
namespace Cut {
  void Dfs1(int u_, int fa_) {
    fa[u_] = fa_;
    size[u_] = 1;
    dep[u_] = dep[fa_] + 1;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa_) continue ;
      Dfs1(v_, u_);
      if (size[v_] > size[son[u_]]) son[u_] = v_;
      size[u_] += size[v_];
    }
  }
  void Dfs2(int u_, int top_) {
    top[u_] = top_;
    if (son[u_]) Dfs2(son[u_], top_);
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa[u_] or v_ == son[u_]) continue ;
      Dfs2(v_, v_);
    }
  }
  int Lca(int u_, int v_) {
    for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
      if (dep[top[u_]] < dep[top[v_]]) {
        std::swap(u_, v_);
      }
    }
    return dep[u_] < dep[v_] ? u_ : v_;
  }
}
//=============================================================
int main() {
  n = read(), m = read(), root = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_), AddEdge(v_, u_);
  }
  Cut::Dfs1(root, 0), Cut::Dfs2(root, root);
  while (m --) {
    int u_ = read(), v_ = read();
    printf("%d\n", Cut::Lca(u_, v_));
  }
  return 0;
}

RMQ

dfs 維護一個序列。
每當一個點被訪問到,就將它加入到序列的尾部。
維護每個節點的深度,在序列中第一次出現的位置。

根據 dfs 的性質,對於節點 x、y,它們第一次出現位置這一段區間內深度最淺的點即為其 LCA。

預處理時間空間復雜度實際上是 \(𝑂(2𝑛 \log⁡(2𝑛))\) 的,單次查詢復雜度 \(O(1)\)
使用局限性比較大,在某些題目中會有作用。

//知識點:LCA
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e5 + 10;
const int kM = kN << 1;
//=============================================================
int n, m, root;
int e_num, head[kN], v[kM], ne[kM], dep[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
namespace ST {
  int num, Log2[kN << 1], f[kN << 1][22], fir[kN];
  void Dfs(int u_, int fa_) {
    dep[u_] = dep[fa_] + 1;
    fir[u_] = ++ num;
    f[num][0] = u_;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa_) continue ;
      Dfs(v_, u_);
      f[++ num][0] = u_;
    }
  }
  void Prepare() {
    Dfs(root, root);
    Log2[1] = 0;
    for (int i = 2; i <= num; ++ i) {
      Log2[i] = Log2[i >> 1] + 1; 
    }
    for (int i = 1; i <= 21; ++ i) {
      for (int j = 1; j + (1 << i) - 1 <= num; ++ j) {
        if (dep[f[j][i - 1]] < dep[f[j + (1 << (i - 1))][i - 1]]) {
          f[j][i] = f[j][i - 1];
        } else {
          f[j][i] = f[j + (1 << (i - 1))][i - 1];
        }
      }
    }
  }
  int Lca(int u_, int v_) {
    int l = fir[u_], r = fir[v_];
    if (l > r) std::swap(l, r);
    int lth = Log2[r - l + 1];
    if (dep[f[l][lth]] < dep[f[r - (1 << lth) + 1][lth]]) {
      return f[l][lth];
    }
    return f[r - (1 << lth) + 1][lth];
  }
}
//=============================================================
int main() {
  n = read(), m = read(), root = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_), AddEdge(v_, u_);
  }
  ST::Prepare();
  for (int i = 1; i <= n; ++ i) {
    int u_ = read(), v_ = read();
    printf("%d\n", ST::Lca(u_, v_));
  }
  return 0;
}

樹的直徑

圖中所有最短路徑的最大值即為「直徑」,可以用兩次 DFS 或者樹形 DP 的方法在 O(n) 時間求出樹的直徑。

之前寫的垃圾文章:「筆記」樹的直徑

樹形 DP

對每個節點維護最長鏈和次長鏈,相加取最大值。

//知識點:樹的直徑 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
int Dfs(int u_, int fa_) {
  int f1 = 0, f2 = 0;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue ;
    int f = Dfs(v_, u_) + 1;
    if (f > f1) {
      f2 = f1;
      f1 = f;
    } else if (f > f2) {
      f2 = f;
    }
  }
  Chkmax(ans, f1 + f2);
  return f1;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_);
    AddEdge(v_, u_);
  }
  Dfs(1, 0);
  printf("%d\n", ans);
}

2 次DFS

距離一個點最遠的點一定是直徑的一個端點。
一次 DfS 找到一個端點,第二次找到另一個。

由於 dis 初值是 0,所以需要 -1。

//知識點:樹的直徑 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int dis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
int Dfs(int u_, int fa_) {
  dis[u_] = dis[fa_] + 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue ;
    Dfs(v_, u_);
  }
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_);
    AddEdge(v_, u_);
  }
  Dfs(1, 0);
  int max_dis = 0, u_ = 0;
  for (int i = 1; i <= n; ++ i) {
    if (dis[i] > max_dis) {
      max_dis = dis[i];
      u_ = i;
    }
    dis[i] = 0;
  }
  Dfs(u_, 0);
  max_dis = 0, u_ = 0;
  for (int i = 1; i <= n; ++ i) {
    if (dis[i] > max_dis) {
      max_dis = dis[i];
      u_ = i;
    }
    dis[i] = 0;
  }
  printf("%d\n", max_dis - 1);
}

判斷負權環

P3385 【模板】負環

Spfa,判斷到達某點最短路上的點數是否 \(>n\)
若為真則有負環。

//知識點:負環 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
int n, m;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn], cnt[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Init() {
  e_num = 0;
  memset(head, 0, sizeof (head));
}
bool SpfaBfs(int s_) {
  std::queue <int> q;
  memset(cnt, 0, sizeof (cnt));
  memset(vis, 0, sizeof (vis));
  memset(dis, 63, sizeof (dis));
  q.push(s_);
  dis[s_] = 0;
  vis[s_] = true;
  
  while (! q.empty()) {
    int u_ = q.front();
    q.pop();
    vis[u_] = false;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u_] + w_ < dis[v_]) {
        dis[v_] = dis[u_] + w_;
        cnt[v_] = cnt[u_] + 1;
        if (cnt[v_] > n) return true;
        if (! vis[v_]) {
          q.push(v_);
          vis[v_] = true;
        }
      }
    }
  }
  return false;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    Init();
    n = read(), m = read(); 
    for (int i = 1; i <= m; ++ i) {
      int u_ = read(), v_ = read(), w_ = read();
      AddEdge(u_, v_, w_);
      if (w_ >= 0) AddEdge(v_, u_, w_);
    }
    printf("%s\n", SpfaBfs(1) ? "YES" : "NO");
  }
  return 0;
}

差分約束

P5960 【模板】差分約束算法

\(x_i -x_j\le k\) 的一堆不等式。
轉化為單源最短路中三角不等式 \(dis_i\le dis_j + k\) 的形式,從 \(j\)\(i\) 連一條權值為 \(k\) 的單向邊。
建立超級源點,與各點距離為 0,跑最短路。

若有負環則無解,否則最短路即為一組合法解。
合法解同加 \(\Delta\) 仍為合法解。

//知識點:負環 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
int n, m;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn], cnt[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Init() {
  e_num = 0;
  memset(head, 0, sizeof (head));
}
bool SpfaBfs(int s_) {
  std::queue <int> q;
  memset(cnt, 0, sizeof (cnt));
  memset(vis, 0, sizeof (vis));
  memset(dis, 63, sizeof (dis));
  q.push(s_);
  dis[s_] = 0;
  vis[s_] = true;
  
  while (! q.empty()) {
    int u_ = q.front();
    q.pop();
    vis[u_] = false;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u_] + w_ < dis[v_]) {
        dis[v_] = dis[u_] + w_;
        cnt[v_] = cnt[u_] + 1;
        if (cnt[v_] > n) return true;
        if (! vis[v_]) {
          q.push(v_);
          vis[v_] = true;
        }
      }
    }
  }
  return false;
}
//=============================================================
int main() {
  n = read(), m = read(); 
  for (int i = 1; i <= m; ++ i) {
    int v_ = read(), u_ = read(), w_ = read();
    AddEdge(u_, v_, w_);
  }
  for (int i = 1; i <= n; ++ i) {
    AddEdge(0, i, 0);
  }
  if (SpfaBfs(0)) {
    printf("NO\n");
    return 0;
  }
  for (int i = 1; i <= n; ++ i) {
    printf("%d ", dis[i]);    
  }
  return 0;
}

強連通相關

縮點

P3387 【模板】縮點

縮點 + Topsort DP。

//知識點:縮點
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = 2e5 + 10;
//=============================================================
int n, m, ans, ori[kMaxn];
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int d_num, b_num, dfn[kMaxn], low[kMaxn], bel[kMaxn], val[kMaxn];
int into[kMaxn], f[kMaxn];
std::vector <int> newv[kMaxn];
std::stack <int> st;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Tarjan(int u_) {
  dfn[u_] = low[u_] = ++ d_num;
  st.push(u_);
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (! dfn[v_]) {
      Tarjan(v_);
      Chkmin(low[u_], low[v_]);
    } else if (! bel[v_]) {
      Chkmin(low[u_], dfn[v_]);
    }
  }
  if (dfn[u_] == low[u_]) { //
    ++ b_num;
    for (int now = 0; now != u_; st.pop()) {
      now = st.top();
      bel[now] = b_num;
      val[b_num] += ori[now];
    }
  }
}
void Topsort() {
  std::queue <int> q;
  for (int i = 1; i <= b_num; ++ i) {
    if (! into[i]) {
      f[i] = val[i];
      q.push(i);
    }
  }
  while (! q.empty()) {
    int u_ = q.front();
    q.pop();
    for (int i = 0, lim = newv[u_].size(); i < lim; ++ i) {
      int v_ = newv[u_][i];
      Chkmax(f[v_], f[u_] + val[v_]);
      into[v_] --;
      if (! into[v_]) {
        q.push(v_);
      }
    }
  }
  for (int i = 1; i <= b_num; ++ i) {
    Chkmax(ans, f[i]);
  }
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) ori[i] = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read();
    if (u_ == v_) continue ;
    AddEdge(u_, v_);
  }
  for (int i = 1; i <= n; ++ i) {
    if (! dfn[i]) Tarjan(i);
  }
  for (int u_ = 1; u_ <= n; ++ u_) {
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (bel[u_] == bel[v_]) continue ;
      newv[bel[u_]].push_back(bel[v_]);
      into[bel[v_]] ++;
    }
  }
  Topsort();
  printf("%d\n", ans);
  return 0;
}

割點

P3388 【模板】割點(割頂)

以下是幾個月前寫的垃圾文字:

應用Tarjan求割點。

按照dfs的遍歷順序,將圖看成一棵樹,圖便有了層次性,將dfs回溯的邊當做回邊。
dfn[] 記錄每一個點的dfs序,low[] 記錄能連通的dfs序最小的點的編號。
初始時 dfn[i] = low[i],在不斷dfs遞歸過程中進行更新。

對於搜索樹的根節點,若它有兩個 或 兩個以上的子樹,去掉此點后, 其各子樹便不連通,說明它是一個割點。

對於其他的節點,更新完成后, 若有一條邊, 滿足 low[v] >= dfn[u]
說明 v 點, 一定到達不了 u 的祖先 (dfs 樹意義下的祖先),則刪去此點后,u 與 v 不連通,u 是一個割點。

//知識點:割點 
/*
By:Luckyblock

*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
#define LL long long
const int kMaxn = 2e4 + 10;
const int kMaxm = 2e5 + 10;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int d_num, dfn[kMaxn], low[kMaxn];
bool cut[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Tarjan(int u_, int root_) {
  int son = 0;
  dfn[u_] = low[u_] = ++ d_num;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (! dfn[v_]) {
      Tarjan(v_, root_);
      Chkmin(low[u_], low[v_]);
      if (u_ == root_) ++ son;
      if (low[v_] >= dfn[u_] && u_ != root_) {
        ans += (! cut[u_]);
        cut[u_] = true;
      }
    } else {
      Chkmin(low[u_], dfn[v_]);
    }
  }
  if (u_ == root_ && son >= 2) {
    ans += (!cut[u_]);
    cut[u_] = true;
  }
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read();
    AddEdge(u_, v_);
    AddEdge(v_, u_);
  }
  for (int i = 1; i <= n; ++ i) {
    if (! dfn[i]) {
      Tarjan(i, i); 
    }
  }
  printf("%d\n", ans);
  for (int i = 1; i <= n; ++ i) {
    if (cut[i]) {
      printf("%d ", i);
    }
  }
  return 0;
}

2-SAT

\(n\) 個 01 變量 \(x_1\sim x_n\),另有 \(m\) 個變量取值需要滿足的限制。
每個限制是一個 \(k\) 元組 \((x_{p_1}, x_{p_2}, \cdots, x_{p_3})\),滿足 \(x_{p_1}\oplus x_{p_2}\oplus\cdots\oplus x_{p_3} = a\)。其中 \(a\)\(0/1\)\(\oplus\) 是某種二元 bool 運算。
要求構造一種滿足所有限制的變量的賦值方案。
建立圖論模型,使用強連通算法實現。

P4782 【模板】-SAT 問題

//知識點:2-SAT
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stack>
#define LL long long
const int kN = 2e6 + 10;
//=============================================================
int n, m, e_num, head[kN], v[kN << 1], ne[kN << 1];
int dfn_num, bel_num, dfn[kN], low[kN], bel[kN];
std::stack <int> st;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Add(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Tarjan(int u_) {
  dfn[u_] = low[u_] = ++ dfn_num;
  st.push(u_);
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (!dfn[v_]) {
      Tarjan(v_);
      Chkmin(low[u_], low[v_]);
    } else if (!bel[v_]) {
      Chkmin(low[u_], dfn[v_]);
    }
  }
  if (dfn[u_] == low[u_]) {
    ++ bel_num;
    while (true) {
      int top = st.top(); st.pop();
      bel[top] = bel_num;
      if (top == u_) break;
    } 
  }
}
//=============================================================
int main() { 
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    int x = read(), a = read(), y = read(), b = read();
    if (a && b) Add(x + n, y), Add(y + n, x);
    if (!a && b) Add(x, y), Add(y + n, x + n);
    if (a && !b) Add(x + n, y + n), Add(y, x);
    if (!a && !b) Add(x, y + n), Add(y, x + n);
  }
  for (int i = 1; i <= 2 * n; ++ i) {
    if (!dfn[i]) Tarjan(i);
    if (i <= n && bel[i] == bel[i + n]) {
      printf("IMPOSSIBLE\n");
      return 0;
    }
  }
  printf("POSSIBLE\n");
  for (int i = 1; i <= n; ++ i) printf("%d ", bel[i] < bel[i + n]); //Tarjan 算法賦給強連通分量的編號順序與拓撲序相反
  return 0; 
}

虛樹

對於樹 \(T=(V,E)\),給定關鍵點集 \(S\subseteq V\),則可定義虛樹 \(T'=(V',E')\)
對於點集 \(V'\subseteq V\),使得 \(u\in V'\) 當且僅當 \(u\in S\),或 \(\exist x,y\in S,\operatorname{lca}(x,y)=u\)
對於邊集,\((u,v)\in E'\),當且僅當 \(u,v\in V'\),且 \(u\)\(v\)\(V'\) 中深度最深的祖先。

個人理解:
僅保留關鍵點及其 \(\operatorname{lca}\),縮子樹成邊,僅保留分叉點,可能刪去一些不包含關鍵點的子樹。
壓縮了樹的信息,同時也丟失了部分樹的信息。
一個分叉點會合並至少兩個關鍵點,虛樹節點數最多為 \(2k-1\) 個。節點數變為了 \(O(k)\) 級別。

建樹考慮增量法,每次向虛樹中添加一個關鍵點。考慮先求得 關鍵節點 的 dfs 序,按照 dfs 序添加關鍵節點。這樣可以保證相鄰兩個關鍵點的 \(\operatorname{lca}\) 深度不小於不相鄰關鍵點的深度。

考慮單調棧維護虛樹最右側的鏈(上一個關鍵點與根的鏈),單調棧中節點深度遞增,棧頂一定為上一個關鍵點。欽定 1 號節點為根,先將其壓入棧中。
每加入一個關鍵點 \(a_i\),令 \(\operatorname{lca}(a_{i-1},a_i)=w\)。將棧頂 \(\operatorname{dep}_x > \operatorname{dep}_w\) 的彈棧,加入 \(w,a_i\),即為新的右鏈。特別地,若棧頂存在 \(\operatorname{dep}_x=\operatorname{dep}_w\),不加入 \(w\) 節點。
在此過程中維護每個節點的父節點,在彈棧時進行連邊並維護信息,即得虛樹。單次建虛樹復雜度 \(O(kw)\) 級別,其中 \(w\) 為單次求 \(\operatorname{lca}\) 的復雜度。

其中 \(\operatorname{Cut}\) 為封裝后的樹鏈剖分。

虛樹上 DP 板題:「SDOI2011」消耗戰

namespace VT { //Virtual Tree
  #define dep Cut::dep
  const int kMaxNode = kN;
  int top, node[kMaxNode], st[kMaxNode]; //棧
  int tag[kMaxNode]; //標記是否為關鍵點
  std::vector <int> newv[kMaxNode]; //虛樹
  bool CMP(int fir_, int sec_) { //按 dfs 序比較
    return Cut::dfn[fir_] < Cut::dfn[sec_];
  }
  void Push(int u_) { //向虛樹中加入 u_
    int lca = Cut::Lca(u_, st[top]);
     //為什么這里要寫成 top - 1?因為 lca 可能出現在棧中,這樣寫便於處理。
    for (; dep[st[top - 1]] > dep[lca]; -- top) {
      newv[st[top - 1]].push_back(st[top]);
    }
    if (lca != st[top]) {
      newv[lca].push_back(st[top]); -- top;
      if (lca != st[top]) st[++ top] = lca;
    }
    if (st[top] != u_) st[++ top] = u_;
  }
  void Build(int siz_) {
    for (int i = 1; i <= siz_; ++ i) {
      node[i] = read();
      tag[node[i]] = 1;
    }
    std::sort(node + 1, node + siz_ + 1, CMP);
    st[top = 0] = 1;
    for (int i = 1; i <= siz_; ++ i) Push(node[i]);
    for (; top; -- top) newv[st[top - 1]].push_back(st[top]);
  }
}

網絡流

網絡最大流

P3376 【模板】網絡最大流

EK

從源點開始 bfs 實現 FF,到達匯點后停止 bfs 並增廣一次。增廣時記錄轉移前驅並更新路徑上邊的殘余容量,答案加上最小流量。

//知識點:網絡最大流,EK
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 210 + 10;
const int kM = 1e4 + 10;
const LL kInf = 9e18 + 2077;
//=============================================================
int n, m, s, t;
int e_num = 1, head[kN], v[kM], ne[kM], from[kN];
LL f[kN], w[kM];
bool vis[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = 1ll * w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
bool Bfs() { //找到一條增廣路
  memset(vis, false, sizeof (vis));
  std::queue <int> q; 
  vis[s] = false;
  f[s] = kInf;
  q.push(s);
  while (!q.empty()) {
    int u_ = q.front(); q.pop();
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i]; 
      LL w_ = w[i];
      if (!w_ || vis[v_]) continue;
      f[v_] = std::min(f[u_], w_);
      vis[v_] = true; 
      from[v_] = i;
      q.push(v_);
      if (v_ == t) return true; 
    }
  }
  return false;
}
LL EK() {
  LL ret = 0;
  while (Bfs()) { //更改路徑的殘余容量
    for (int u_ = t; u_ != s; u_ = v[from[u_] ^ 1]) {
      w[from[u_]] -= f[t]; 
      w[from[u_] ^ 1] += f[t];
    }
    ret += f[t];
  }
  return ret; 
}
//=============================================================
int main() {
  n = read(), m = read(), s = read(), t = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_);
    AddEdge(v_, u_, 0);
  }
  printf("%lld\n", EK());
  return 0;
}

Dinic

使用 dfs 找增廣路,不過在每輪增廣前使用 bfs 將殘余網絡分層。一個點的層數為殘余網絡上它與源點的最小邊數。分層時若匯點的層數不存在,說明不存在增廣路,即可停止增廣。
之后 dfs 增廣時,每次轉移僅向比當前點層數多 1 的點轉移。這可以保證每次找到的增廣路是最短的。

多路增廣 + 當前弧優化。

//知識點:網絡最大流,Dinic
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 210 + 10;
const int kM = 1e4 + 10;
const LL kInf = 9e18 + 2077;
//=============================================================
int n, m, s, t;
int e_num = 1, head[kN], v[kM], ne[kM];
int cur[kN], dep[kN];
LL w[kM];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = 1ll * w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
bool Bfs() {
  std::queue <int> q;
  memset(dep, 0, sizeof (dep));
  dep[s] = 1; //注意初始化 
  q.push(s); 
  while (!q.empty()) {
    int u_ = q.front(); q.pop();
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      LL w_ = w[i];
      if (w_ > 0 && !dep[v_]) { 
        dep[v_] = dep[u_] + 1;
        q.push(v_);
      }
    }
  }
  return dep[t];
}
LL Dfs(int u_, LL into_) {
  if (u_ == t || !into_) return into_;
  LL out = 0;
  for (int& i = cur[u_]; i && into_; i = ne[i]) { //當前弧優化 
    int v_ = v[i];
    LL w_ = w[i];
    if (dep[v_] == dep[u_] + 1 && w_ > 0) {
      LL ret = Dfs(v_, std::min(into_, w_));
      if (!ret) dep[v_] = kN; //沒有貢獻,之后不會再訪問 
      w[i] -= ret, w[i ^ 1] += ret;
      out += ret, into_ -= ret;
    }
  }
  return out;
}
LL Dinic() {
  LL ret = 0;
  while (Bfs()) {
    for (int i = 1; i <= n; ++ i) cur[i] = head[i]; //初始化當前弧 
    ret += Dfs(s, kInf);
  }
  return ret;
}
//=============================================================
int main() {
  n = read(), m = read(), s = read(), t = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_);
    AddEdge(v_, u_, 0);
  }
  printf("%lld\n", Dinic());
  return 0;
}

二分圖匹配

P3386 【模板】二分圖最大匹配

匈牙利

枚舉一邊的點,找增廣路。
復雜度 \(O(n^2 m)\)

//知識點:二分圖最大匹配 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 500 + 10;
const int kMaxe = 5e4 + 10;
//=============================================================
int n, m, e, ans;
int e_num, head[kMaxn], v[kMaxe], ne[kMaxe];
int match[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
bool Dfs(int u_) {
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (vis[v_]) continue ;
    vis[v_] = true;
    if (! match[v_] or Dfs(match[v_])) {
      match[v_] = u_;
      return true;
    }
  }
  return false;
}
//=============================================================
int main() {
  n = read(), m = read(), e = read();
  for (int i = 1; i <= e; ++ i) {
    int u_ = read(), v_ = read();
    if (u_ > n || v_ > m) continue ;
    AddEdge(u_, v_);
  }
  for (int i = 1; i <= n; ++ i) {
    memset(vis, 0, sizeof (vis));
    if (Dfs(i)) ++ ans;
  }
  printf("%d\n", ans);
  return 0;
}

網絡流

建模,在一個匹配中, 任意兩條邊都沒有共同的端點,每一個端點 最多只有 1 的貢獻。 在網絡流中,最多只有 1 的貢獻可轉化為 邊的容量為 1。由此可對原二分圖進行轉化:

新建源點 與 匯點。

  • 源點向每個左側點 各連一條容量為 1 的有向邊。
  • 原圖中的邊改為 自左向右的有向邊,容量為 1。
  • 每個右側點向匯點 各連一條容量為 1 的有向邊。

求最大流即為最大匹配。

線段樹優化建圖

用於解決區間連邊的一個建圖小技巧。

對於區間連邊問題,其解決方案是建立一系列虛點,虛點到實點的權值為 0,一個虛點與某一段連續區間內的實點相連。此時若想對該區間進行區間連邊,直接連向此虛點即可。
為解決區間的拆分問題,可以將虛點建成一個如下圖所示的類似線段樹的結構,使得區間的拆分可以放到線段樹上進行。

CF786B Legacy

//知識點:線段樹優化建圖 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 4e5 + 10;
const LL kInf = 0x3f3f3f3f3f3f3f3f;
//=============================================================
int n, q, start, node_num;
int e_num, head[kN], v[kN << 4], w[kN << 4], ne[kN << 4];
bool vis[kN];
LL dis[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Add(int u_, int v_, int w_) {
  v[++ e_num] = v_, w[e_num] = w_;
  ne[e_num] = head[u_], head[u_] = e_num;
}
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
struct SegmentTree {
  int root, lson[kN], rson[kN];
  void Build(int &now_ ,int L_, int R_, bool type_) {
    if (L_ == R_) {
      now_ = L_;
      return ;
    }
    now_ = ++ node_num;
    Build(ls, L_, mid, type_);
    Build(rs, mid + 1, R_, type_);
    if (!type_) Add(now_, ls, 0), Add(now_, rs, 0);
    if (type_) Add(ls, now_, 0), Add(rs, now_, 0);
  }
  void Modify(int now_, int L_, int R_, int l_, int r_, int u_, int w_, 
              bool type_) {
    if (l_ <= L_ && R_ <= r_) {
      if (!type_) Add(u_, now_, w_);
      if (type_) Add(now_, u_, w_);
      return ;
    }
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, u_, w_, type_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, u_, w_, type_);
  }
} Seg[2];
#undef ls
#undef rs
#undef mid
void Init() {
  node_num = n;
  Seg[0].Build(Seg[0].root, 1, n, 0);
  Seg[1].Build(Seg[1].root, 1, n, 1);
}
void Spfa(int s_) {  //他死了
  std::queue <int> q;
  memset(dis, 0x3f, sizeof(dis));
  dis[s_] = 0;
  q.push(s_);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int i = head[u]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u] + w_ < dis[v_]) {
        dis[v_] = dis[u] + w_;
        q.push(v_);
      }
    }
  }
}
#define pr std::pair
#define mp std::make_pair
void Dijkstra(int s_) {
  std::priority_queue <pr <LL, int> > q;
  memset(dis, 63, sizeof (dis));
  dis[s_] = 0;
  q.push(mp(0, s_));
  while (! q.empty()) {
    int u_ = q.top().second; q.pop();
    if (vis[u_]) continue;
    vis[u_] = true;
    
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u_] + w_ < dis[v_]) {
        dis[v_] = dis[u_] + w_;
        q.push(mp(-dis[v_], v_));
      }
    }
  }
}
#undef pr
#undef mp
//=============================================================
int main() {
  n = read(), q = read(), start = read();
  Init();
  while (q --) {
    int opt = read();
    if (opt == 1) {
      int u_ = read(), v_ = read(), w_ = read();
      Add(u_, v_, w_);
    } else {
      int u_ = read(), l_ = read(), r_ = read(), w_ = read();
      int type = (opt == 3);
      Seg[type].Modify(Seg[type].root, 1, n, l_, r_, u_, w_, type);
    }
  }
  Dijkstra(start);
  for (int i = 1; i <= n; ++ i) {
    printf("%lld ", dis[i] < kInf ? dis[i] : -1); 
  }
  return 0;
}

字符串

Trie

字典樹。

namespace Trie {
  const int kMaxNode = kN << 5;
  int node_num, tr[kMaxNode][2];
  LL val[kMaxNode];
  void Insert(LL val_) {
    int now_  = 0;
    for (LL i = 33; i >= 0; -- i) {
      int ch = val_ >> i & 1ll;
      if (!tr[now_][ch]) tr[now_][ch] = ++ node_num;
      now_ = tr[now_][ch];
    }
    val[now_] = val_;
  }
}

可持久化 Trie

Q:什么時候需要可持久化?
A:想建一車 Trie 但是建不下的時候。

其實我很疑惑該把這個加到數據結構還是字符串里的= =

P4735 最大異或和

可持久化 Trie 可以將全局的二進制運算問題轉化到區間上進行。

//知識點:
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stack>
#define LL long long
const int kN = 6e6 + 10;
//=============================================================
int n, m, a[kN], sum[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace Trie {
  #define ls (tr[now_][0])
  #define rs (tr[now_][1])
  const int kMaxRoot = kN;
  const int kMaxNode = kN << 2;
  int node_num, root[kMaxRoot], tr[kMaxNode][2], last[kMaxNode];
  void Insert(int &now_, int pre_, int val_, int lth_, int id_) {
    now_ = ++ node_num;
    ls = tr[pre_][0], rs = tr[pre_][1], last[now_] = id_;
    if (lth_ < 0) return ;
    int ch = val_ >> lth_ & 1;
    Insert(tr[now_][ch], tr[pre_][ch], val_, lth_ - 1, id_);
  }
  int Query(int now_, int val_, int lth_, int l_) {
    if (lth_ < 0) return sum[last[now_]];
    int ch = val_ >> lth_ & 1;
    if (last[tr[now_][ch ^ 1]] >= l_) {
      return Query(tr[now_][ch ^ 1], val_, lth_ - 1, l_);
    }
    return Query(tr[now_][ch], val_, lth_ - 1, l_);
  }
}
void Init() {
  n = read(), m = read();
  Trie::last[0] = -1;
  Trie::Insert(Trie::root[0], 0, 0, 25, 0);
  for (int i = 1; i <= n; ++ i) {
    sum[i] = sum[i - 1] ^ read();
    Trie::Insert(Trie::root[i], Trie::root[i - 1], sum[i], 25, i);
  }
}
//=============================================================
int main() { 
  Init();
  while (m --) {
    char opt[5]; scanf("%s", opt + 1);
    if (opt[1] == 'A') {
      ++ n;
      sum[n] = sum[n - 1] ^ read(); 
      Trie::Insert(Trie::root[n], Trie::root[n - 1], sum[n], 25, n);
    } else {
      int l = read(), r = read(), val = sum[n] ^ read();
      printf("%d\n", Trie::Query(Trie::root[r - 1], val, 25, l - 1) ^ val);
    }
  }
  return 0; 
}

KMP

子串匹配問題。在朴素算法中,如果在某一位上失配,則會拋棄當前已匹配的部分,跳到下一個位置再從開頭進行匹配。
而 KMP 利用了當前已匹配的部分,使得在下一個位置時不必從開頭進行匹配,從而對朴素算法進行了加速。

P3375 【模板】KMP 字符串匹配

//知識點:KMP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
char s1[kN], s2[kN];
int n1, n2;
int fail[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
//=============================================================
int main() {
  scanf("%s", s1 + 1);
  scanf("%s", s2 + 1);
  n1 = strlen(s1 + 1), n2 = strlen(s2 + 1);

  fail[1] = 0;
  for (int i = 2, j = 0; i <= n2; ++ i) {
    while (j > 0 && s2[i] != s2[j + 1]) j = fail[j];
    if (s2[i] == s2[j + 1]) ++ j;
    fail[i] = j;
  }
  for (int i = 1, j = 0; i <= n1; ++ i) {
    while (j > 0 && (j == n2 || s1[i] != s2[j + 1])) j = fail[j];
    if (s1[i] == s2[j + 1]) ++ j;
    if (j == n2) printf("%d\n", i - n2 + 1);
  }
  for (int i = 1; i <= n2; ++ i) printf("%d ", fail[i]);
  return 0;
}

AC 自動機

可以認為是 KMP 算法在 Trie 樹上的應用,與 KMP 算法在失配時應用已匹配部分的 border 進行跳轉類似,AC 自動機在失配時會根據失配指針跳轉到 Trie 樹上代表已匹配部分的 border 的節點,從而加速匹配。

P5357 【模板】AC自動機

//知識點:ACAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#define LL long long
const int kT = 2e6 + 10;
const int kN = 2e5 + 10;
//=============================================================
int n;
char s[kT];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace ACAM {
  std::vector <int> id[kN];
  int node_num, tr[kN][26], sum[kN], fail[kN];
  int e_num, size[kN], head[kN], v[kN], ne[kN];
  void Add(int u_, int v_) {
    v[++ e_num] = v_;
    ne[e_num] = head[u_];
    head[u_] = e_num;
  }
  void Dfs(int u_) {
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      Dfs(v_);
      size[u_] += size[v_];
    }
    for (int i = 0, lim = id[u_].size(); i < lim; ++ i) {
      sum[id[u_][i]] = size[u_];
    }
  }
  void Insert(int id_, char *s_) {
    int now = 0, lth = strlen(s_ + 1);
    for (int i = 1; i <= lth; ++ i) {
      if (! tr[now][s_[i] - 'a']) tr[now][s_[i] - 'a'] = ++ node_num;
      now = tr[now][s_[i] - 'a'];
    }
    id[now].push_back(id_);
  }
  void Build() {
    std::queue <int> q;
    for (int i = 0; i < 26; ++ i) {
      if (tr[0][i]) q.push(tr[0][i]);
    }
    while (! q.empty()) {
      int u_ = q.front(); q.pop();
      for (int i = 0; i < 26; ++ i) {
        if (tr[u_][i]) {
          fail[tr[u_][i]] = tr[fail[u_]][i];
          q.push(tr[u_][i]);
        } else {
          tr[u_][i] = tr[fail[u_]][i];
        }
      }
    }
  }
  void Query(char *t_) {
    int now = 0, lth = strlen(t_ + 1);
    for (int i = 1; i <= lth; ++ i) {
      now = tr[now][t_[i] - 'a'];
      ++ size[now];
    }
    for (int i = 1; i <= node_num; ++ i) Add(fail[i], i);
    Dfs(0);
    for (int i = 1; i <= n; ++ i) printf("%d\n", sum[i]);
  }
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) {
    scanf("%s", s + 1);
    ACAM::Insert(i, s); 
  }
  ACAM::Build();
  scanf("%s", s + 1);
  ACAM::Query(s);
  return 0;
}

最小表示法

當字符串 \(S\) 中可以選定一個位置 \(i\) 滿足:

\[S[i:n]+S[1:i-1]=T \]

則稱 \(S\)\(T\) 循環同構

字符串 \(S\)最小表示 為與 \(S\) 循環同構的所有字符串中字典序最小的字符串。

最小表示法思想與 KMP 類似,都是重復利用之前比較過的部分,減少時間復雜度。
學習筆記:「最小表示法」P1368 工藝 /【模板】最小表示法

P1368 工藝 /【模板】最小表示法

//知識點:最小表示法 
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 3e5 + 10;
//=============================================================
int n, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
int MinRep() {
  int i = 0, j = 1, k = 0;
  while (i < n && j < n && k < n) {
    int delta = a[(i + k) % n] - a[(j + k) % n];
    if (delta == 0) {
      ++ k;
      continue ;
    }
    if (delta > 0) i += k + 1;
    else j += k + 1;
    if (i == j) j ++;
    k = 0;
  }
  return std :: min(i, j);
}
//=============================================================
int main() {
  n = read();
  for (int i = 0; i < n; ++ i) a[i] = read();
  for (int i = MinRep(), j = 0; j < n; ++ j) {
    printf("%d ", a[(i + j) % n]);
  }
  return 0;
}

后綴數組

字符串 \(S\) 的后綴數組定義為兩個數組 \(sa,rk\)
\(sa\) 儲存 \(S\) 的所有后綴 按字典序排序后的起始下標,滿足 \(S[sa_{i-1}:n]<S[sa_i:n]\)
\(rk\) 儲存 \(S\) 的所有后綴的排名。
結合后綴的最長公共前綴,后綴數組可以用來處理許多復雜度字符串問題。

「筆記」后綴數組

UOJ#35. 后綴排序

//知識點:SA
/*
By:Luckyblock
I love Marisa;
But Marisa has died;
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e6 + 10;
//=============================================================
char S[kMaxn];
//rk[i]: 倍增過程中子串[i:i+2^k-1]的排名,
//sa[i] 排名為i的子串 [i:i+2^k-1],
//它們互為反函數。
//存在越界風險,如果不寫特判,rk 和 oldrk 要開 2 倍空間。
int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1];
int id[kMaxn], cnt[kMaxn]; //用於計數排序的兩個 temp 數組
int height[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetHeight() {
  for (int i = 1, k = 0; i <= n; ++ i) {
      if (rk[i] == 1) {
        k = 0;
      } else {
        if (k) -- k;
        int j = sa[rk[i] - 1];
        while (i + k <= n && j + k <= n && 
               S[i + k] == S[j + k]) {
          ++ k;
        }
      }
      height[rk[i]] = k;
    }
}

//=============================================================
int main() {
  scanf("%s", S + 1);
  n = strlen(S + 1);
  m = std :: max(n, 300); //值域大小
  
  //初始化 rk 和 sa
  for (int i = 1; i <= n; ++ i) ++ cnt[rk[i] = S[i]];
  for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;

  //倍增過程。w 是已經推出的子串長度,數值上等於上述分析中的 2^{k-1}。
  //注意此處的 sa 數組存的並不是后綴的排名,存的是倍增過程中指定長度子串的排名。
  for (int w = 1; w < n; w <<= 1) {
    //按照后半截 rk[i+w] 作為第二關鍵字排序。
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= n; ++ i) id[i] = i;
    for (int i = 1; i <= n; ++ i) ++ cnt[rk[id[i] + w]]; //越界風險,2倍空間
    for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; -- i) sa[cnt[rk[id[i] + w]] --] = id[i];

    //按照前半截 rk[i] 作為第一關鍵字排序。
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= n; ++ i) id[i] = sa[i];
    for (int i = 1; i <= n; ++ i) ++ cnt[rk[id[i]]];
    for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; -- i) sa[cnt[rk[id[i]]] --] = id[i];

    //更新 rk 數組,可以滾動數組一下,但是可讀性會比較差(
    for (int i = 1; i <= n; ++ i) oldrk[i] = rk[i];
    for (int p = 0, i = 1; i <= n; ++ i) {
      if (oldrk[sa[i]] == oldrk[sa[i - 1]] &&  //判斷兩個子串是否相等。
          oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) { //越界風險,2倍空間
        rk[sa[i]] = p;
      } else {
        rk[sa[i]] = ++ p;
      }
    }
  }
  for (int i = 1; i <= n; ++ i) printf("%d ", sa[i]);
  printf("\n");
  GetHeight();
  for (int i = 2; i <= n; ++ i) printf("%d ", height[i]);
  return 0;
}

后綴自動機

「筆記」后綴自動機

字符串 \(S\) 的后綴自動機 (suffix automaton, SAM) 是一個可以且盡可以接受 \(S\) 所有后綴的 最小的 DFA。
更形式化的定義:

  • 字符串 \(S\) 的 SAM 是一張 DAWG(有向單詞無環圖)。節點被稱作 狀態,邊被稱作狀態間的 轉移
  • 存在一個起始節點,稱作 起始狀態。其它節點均可從起始節點出發到達。
  • 每個 轉移 都標有一些字母。從一個節點出發的所有轉移均 不同
  • 存在數個 終止狀態。若從 \(t_0\) 出發,最終轉移到一個終止狀態,路徑上所有轉移連接起來一定是 \(S\) 的一個后綴。\(S\) 的每個后綴均可用一條 從起始節點到某個終止狀態 的路徑構成。
  • 在所有滿足上述條件的 DFA 中,SAM 的節點數是最少的。

SAM 並不是一個典型的 DFA,在 DAWG 基礎上,除 \(t_0\) 外的每個狀態都被添加了一條 后綴鏈接。所有后綴鏈接組成了樹狀結構,這棵樹被稱為 parent 樹

字符串 \(S\) 的 SAM 能包含 \(S\) 所有子串的信息。
SAM 將這些信息以高度壓縮的形式儲存,對於一個長度為 \(n\) 的字符串,它的 SAM 空間復雜度僅為 \(O(n)\),構建 SAM 的時間復雜度也僅為 \(O(n)\)

P3804 【模板】后綴自動機 (SAM)

//知識點:SAM
/*
By:Luckyblock
試了試變量寫法,挺清爽的。
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
int n, last = 1, node_num = 1; //注意 1 為起始狀態,其他狀態的標號從 1 開始。 
int ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1]; //SAM
int edge_num, v[kMaxn << 1], ne[kMaxn << 1], head[kMaxn << 1]; //Graph
ll ans, size[kMaxn << 1];
char S[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(ll &fir, ll sec) {
  if (sec > fir) fir = sec;
}
void AddEdge(int u_, int v_) {
  v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Insert(int c_) { //原串 S -> 新串 S + c
  int p = last, now = last = ++ node_num;
  size[now] = 1, len[now] = len[p] + 1; //僅為終止狀態 size 賦值。
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  
  //case 1:
  if (! p) {link[now] = 1; return ;} 

  //case 2:
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return ;}

  //case 3:
  int newq = ++ node_num;
  len[newq] = len[p] + 1; 
  memcpy(ch[newq], ch[q], sizeof(ch[q])); 

  link[newq] = link[q];
  link[q] = link[now] = newq; 
  
  for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}

//parent 樹上 DP,用孩子更新父親。
//孩子的長度比父親長,則孩子出現的地方,父親一定出現,size 累加。
//父親的 size 也可能不為 0(為終止狀態)。
void Dfs(int u) { 
  for (int i = head[u]; i; i = ne[i]) {
    Dfs(v[i]);
    size[u] += size[v[i]];
  }
  if (size[u] > 1) GetMax(ans, 1ll * size[u] * len[u]);
}
//=============================================================
int main() {
  scanf("%s", S + 1);
  int n = strlen(S + 1);
  for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a');
  for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
  Dfs(1);
  printf("%lld\n", ans);
  return 0; 
}

廣義后綴自動機

「筆記」廣義后綴自動機

廣義 SAM 是一種用於維護 Trie 的子串信息的 SAM 的簡單變體。

將多個模式串插入到 Trie 后,即可使用廣義 SAM 維護多模式串的信息。這是廣義 SAM 最廣泛的應用之一,本文的重點也在於此。其基本思想是將多串的信息進行壓縮,使得 SAM 在仍滿足節點數最少的同時 包含所有子串的信息。此時 SAM 中的一個狀態可能同時代表多個串中相應的子串。

P6139 【模板】廣義后綴自動機(廣義 SAM)

//知識點:SAM
/*
By:Luckyblock
試了試變量寫法,挺清爽的。
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
ll ans;
char S[kMaxn];
int node_num = 1, ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
int Insert(int c_, int last_) {
  if (ch[last_][c_]) {
    int p = last_, q = ch[p][c_];
    if (len[p] + 1 == len[q]) return q;
    int newq = ++ node_num;
    memcpy(ch[newq], ch[q], sizeof(ch[q])); 
    len[newq] = len[p] + 1; 
    link[newq] = link[q];
    link[q] = newq; 
    for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
    return newq;
  }
  int p = last_, now = ++ node_num;
  len[now] = len[p] + 1;
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  if (! p) {link[now] = 1; return now;} 
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return now;}
  int newq = ++ node_num;
  memcpy(ch[newq], ch[q], sizeof(ch[q])); 
  link[newq] = link[q], len[newq] = len[p] + 1; 
  link[q] = link[now] = newq;
  for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
  return now;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    scanf("%s", S + 1);
    int n = strlen(S + 1), last = 1;
    for (int i = 1; i <= n; ++ i) {
      last = Insert(S[i] - 'a', last);
    }
  }
  for (int i = 2; i <= node_num; ++ i) {
    ans += len[i] - len[link[i]];
  }
  printf("%lld\n", ans);
  return 0; 
}

數學

快速冪

P1226 【模板】快速冪||取余運算

轉化為 \(\prod a^{2^?}\)

//知識點:快速冪 
/*
By:Luckyblock
注意 p = 0 
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
//=============================================================
LL a, b, p;
//=============================================================
inline LL read() {
  LL f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3ll) + (w << 1ll) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_, LL mod_) {
  LL ret = 1;
  x_ %= mod_;
  for (; y_; y_ >>= 1ll) {
    if (y_ & 1) ret = ret * x_ % mod_;
    x_ = x_ * x_ % mod_;
  }
  return ret % mod_;
}
//=============================================================
int main() {
  LL a = read(), b = read(), p = read();
  printf("%lld^%lld mod %lld=%lld", a, b, p, QPow(a, b, p));
  return 0;
}

線性篩

P3383 【模板】線性篩素數

每個數只被它的最小質因子篩掉。

//知識點:先行篩 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e8 + 10;
//=============================================================
int n, q;
int p_num, p[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void GetPrime(int n_) {
  for (int i = 2; i <= n_; ++ i) {
    if (! vis[i]) {
      p[++ p_num] = i; 
    }
    for (int j = 1; j <= p_num; ++ j) {
      if (i * p[j] > n_) break;
      vis[i * p[j]] = true;
      if (i % p[j] == 0) break;
    }
  }
}
//=============================================================
int main() {
  n = read(), q = read();
  GetPrime(n);
  while (q --) {
    int k = read();
    printf("%d\n", p[k]);
  }
  return 0;
}

線性求逆元

P3811 【模板】乘法逆元

\(p\in \mathbb{P}\),則有:

\[\operatorname{inv}(i) = \left(p - \left\lfloor\frac{p}{i}\right\rfloor\right)\times \operatorname{inv}(i \bmod p) \pmod p \]

//知識點:線性求逆元 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 3e6 + 10;
//=============================================================
int n, p, inv[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  n = read(), p = read();
  inv[1] = 1;
  for (int i = 2; i <= n; ++ i) {
    inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
  }
  for (int i = 1; i <= n; ++ i) {
    printf("%d\n", inv[i]);
  }
  return 0;
}

矩陣快速冪

P3390 【模板】矩陣快速冪

矩陣乘法:

\[\begin{aligned} A\times B &= \begin{bmatrix} a_{1,1}& a_{1,2}\\ a_{2,1}& a_{2,2} \end{bmatrix} \times \begin{bmatrix} b_{1,1}& b_{1,2}\\ b_{2,1}& b_{2,2} \end{bmatrix}\\ &= \begin{bmatrix} \sum a_{1,i} b_{i,1}& \sum a_{1,i}b_{i,2}\\ \sum a_{2,i} b_{i,1}& \sum a_{2,i}b_{i,2} \end{bmatrix} \end{aligned}\]

矩陣滿足結合律,故可拆分為 \(\prod A^{2^?}\),使用快速冪求解。

//知識點:矩陣乘法,矩陣快速冪
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxm = 100 + 1;
const LL kMod = 1e9 + 7;
//=============================================================
int n;
LL b;
struct Matrix {
  LL a[kMaxm][kMaxm];
  void build() {
    for (int i = 1; i <= n; ++ i) {
      a[i][i] = 1;
    }
  }
  Matrix operator * (const Matrix &b_) const {
    Matrix ret;
    memset(ret.a, 0, sizeof (ret.a));
    for (int k = 1; k <= n; ++ k) {
      for (int i = 1; i <= n; ++ i) {
        for (int j = 1; j <= n; ++ j) {
          ret.a[i][j] += a[i][k] * b_.a[k][j] % kMod;
          ret.a[i][j] %= kMod;
        }
      }
    }
    return ret;
  }
} a;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
Matrix QMatrixPow(Matrix a_, LL b_) {
  Matrix ret;
  ret.build();
  for (; b_; b_ >>= 1) {
    if (b_ & 1) ret = ret * a_;
    a_ = a_ * a_;
  }
  return ret;
}
//=============================================================
int main() {
  n = read(); scanf("%lld", &b);
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      a.a[i][j] = read();
    }
  }
  Matrix ans = QMatrixPow(a, b);
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      printf("%d ", ans.a[i][j]);
    }
    printf("\n");
  }
  return 0;
}

三分法

P3382 【模板】三分法

之前講課的課件:「二分與三分」課件配套資料

//知識點:三分法求函數極值 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 13 + 5;
const double eps = 1e-7;
//=============================================================
int n;
double l, r, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
double f(double x_) {
  double ret = 0;
  for (int i = 1; i <= n + 1; ++ i) {
    ret = ret * x_ + a[i];
  }
  return ret;
}
//=============================================================
int main() {
  n = read();
  scanf("%lf %lf", &l, &r);
  for (int i = 1; i <= n + 1; ++ i) scanf("%lf", &a[i]);
  while (r - l > eps) {
    double lx = (2.0 * l + r) / 3, rx = (l + 2.0 * r) / 3;
    if (f(lx) < f(rx)) {
      l = lx;
    } else {
      r = rx;
    }
  }
  printf("%.5lf", l);
  return 0;
}

高斯消元

P3389 【模板】高斯消元法

小學解方程。

//知識點:高斯消元 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
const double eps = 1e-7;
const int kMaxn = 100 + 10;
//=============================================================
int n;
double a[kMaxn][kMaxn], ans[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n + 1; ++ j) {
      scanf("%lf", &a[i][j]);
    }
  }
  for (int i = 1; i <= n; ++ i) { //每次將第 i 行第 i 個元素消為 1。 
    int pos = i;  //每次找到最大系數消除,減小誤差 
    for (int j = i + 1; j <= n; ++ j) {
      if (fabs(a[j][i]) > fabs(a[pos][i])) {
        pos = j;
      }
    }
    
    if (fabs(a[pos][i]) < eps) { //系數為 0,無解 
      printf("No Solution");
      return 0;
    }
    
    for (int j = 1; j <= n + 1; ++ j) { //交換到第 i 行 
      std::swap(a[pos][j], a[i][j]);
    }
    
    double tmp = a[i][i]; //將第 i 行第 i 個消成 1,同時處理該行其他系數 
    for (int j = i; j <= n + 1; ++ j) {
      a[i][j] /= tmp;
    }
    for (int j = i + 1; j <= n; ++ j) { //枚舉其他行 
      tmp = a[j][i]; //該行對應系數 
      for (int k = i; k <= n + 1; ++ k) {
        a[j][k] -= a[i][k] * tmp; //注意第 i 行的系數均已被處理過 
      }
    }
  }
  
  ans[n] = a[n][n + 1];
  for (int i = n - 1; i >= 1; -- i) {
    ans[i] = a[i][n + 1];
    for (int j = i + 1; j <= n; ++ j) { //回帶
      ans[i] -= a[i][j] * ans[j];
    }
  }
  
  for (int i = 1; i <= n; ++ i) {
    printf("%.2lf\n", ans[i]);
  }
  return 0;
}

盧卡斯定理

\[\dbinom{n}{m} = \begin{pmatrix} {\left\lfloor\frac{n}{p}\right\rfloor}\\{\left\lfloor\frac{m}{p}\right\rfloor}\end{pmatrix}\cdot \dbinom{n\bmod p}{m\bmod p}\pmod p\]

//知識點:Lucas 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxp = 1e5 + 10;
//=============================================================
LL p, fac[kMaxp];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_) {
  x_ %= p;
  LL ret = 1ll;
  for (; y_; y_ >>= 1ll) {
    if (y_ & 1) ret = ret * x_ % p;
    x_ = x_ * x_ % p;
  }
  return ret;
}
LL C(LL n_, LL m_) {
  if (m_ > n_) return 0ll;
  return fac[n_] * QPow(fac[m_], p - 2ll) * QPow(fac[n_ - m_], p - 2ll);
}
LL Lucas(LL n_, LL m_) {
  if (! m_) return 1;
  return Lucas(n_ / p, m_ / p) * C(n_ % p, m_ % p) % p;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    int n = read(), m = read();
    p = read(); 
    fac[0] = 1;
    for (int i = 1; i <= p; ++ i) {
      fac[i] = 1ll * fac[i - 1] * i % p;
    }
    printf("%lld\n", Lucas(n + m, m));
  }
  return 0;
}

數學亂記

懶得往這搬了= =

「筆記」數學亂記

雜項

CDQ 分治

Cdq 分治是一種牛逼思想,一般用於解決區間點對問題。設當前處理的區間為 \([l, r]\),Cdq 分治的一般過程:

  1. \(l = r\),返回。
  2. 設區間中點為 \(mid\),遞歸處理 \([l,mid]\)\([mid + 1, r]\)
  3. 不橫跨 \(mid\) 的點對都會在遞歸中被解決,僅考慮橫跨 \(mid\) 的點對的貢獻。

個人理解:
\(O(n^2)\) 級別的點對統計,利用單調性確定合法性,變為 \(O(n\log^k n)\) 級別。
太神了!

P3810 【模板】三維偏序(陌上花開)

//知識點:CDQ分治 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#define ll long long
#define lowbit(x) (x&-x)
#define mid ((l_+r_)>>1)
const int kMaxn = 1e5 + 10;
//=============================================================
struct Data {
  int x, y, z, cnt, ans;
} a[kMaxn];
int n, k, ans[kMaxn], t[kMaxn << 1];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool Compare1(Data fir_, Data sec_) {
  if (fir_.x != sec_.x) return fir_.x < sec_.x;
  if (fir_.y != sec_.y) return fir_.y < sec_.y;
  return fir_.z < sec_.z;
}
bool Compare2(Data fir_, Data sec_) {
  if (fir_.y != sec_.y) return fir_.y < sec_.y;
  return fir_.z < sec_.z;
}
void Add(int pos_, int val_) {
  for (; pos_ <= k; pos_ += lowbit(pos_)) {
    t[pos_] += val_;
  }
}
int Sum(int pos_) {
  int ret = 0;
  for (; pos_; pos_ -= lowbit(pos_)) ret += t[pos_];
  return ret;
}
void Cdq(int l_, int r_) {
  if (l_ == r_) return ;
  Cdq(l_, mid), Cdq(mid + 1, r_);
  std :: sort(a + l_, a + mid + 1, Compare2);
  std :: sort(a + mid + 1, a + r_ + 1, Compare2);
  int i = l_, j = mid + 1;
  for (; j <= r_; ++ j) {
    for (; a[i].y <= a[j].y && i <= mid; ++ i) {
      Add(a[i].z, a[i].cnt);
    }
    a[j].ans += Sum(a[j].z);
  }
  for (j = l_; j < i; ++ j) Add(a[j].z, - a[j].cnt); 
}
bool JudgeEqual(Data fir_, Data sec_) {
  if (fir_.x != sec_.x) return false;
  if (fir_.y != sec_.y) return false;
  return fir_.z == sec_.z;
}
//=============================================================
int main() {
  int tmpn = n = read();
  k = read();
  for (int i = 1; i <= n; ++ i) {
    a[i] = (Data) {read(), read(), read()};
  }
  std :: sort(a + 1, a + n + 1, Compare1);
  n = 0;
  for (int i = 1, cnt = 0; i <= tmpn; ++ i) {
    cnt ++;
    if (! JudgeEqual(a[i], a[i + 1])) {
      a[++ n] = a[i], 
      a[n].cnt = cnt,
      cnt = 0;
    }
  }
  Cdq(1, n);
  for (int i = 1; i <= n; ++ i) {
    ans[a[i].ans + a[i].cnt - 1] += a[i].cnt;
  }
  for (int i = 0; i < tmpn; ++ i) {
    printf("%d\n", ans[i]);
  }
  return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM