跳转至

Print wf

动态规划

数据结构

平衡树

无旋 Treap

#include "../../header.cpp"
mt19937_64 MT(114514);
namespace Treap{
  const int SIZ = 1e6 + 1e5 + 3;
  int F[SIZ], C[SIZ], S[SIZ], W[SIZ], X[SIZ][2], sz;
  u64 H[SIZ];
  int  newnode(int w){
    W[++ sz] = w, C[sz] = S[sz] = 1, H[sz] = MT();
    return sz;
  }
  void pushup(int x){
    S[x] = C[x] + S[X[x][0]] + S[X[x][1]];
  }
  pair<int, int> split(int u, int x){
    if(u == 0)
      return make_pair(0, 0);
    if(W[u] > x){
      auto [a, b] = split(X[u][0], x);
      X[u][0] = b, pushup(u);
      return make_pair(a, u);
    } else {
      auto [a, b] = split(X[u][1], x);
      X[u][1] = a, pushup(u);
      return make_pair(u, b);
    }
  }
  int merge(int a, int b){
    if(a == 0 || b == 0)
      return a | b;
    if(H[a] < H[b]){
      X[a][1] = merge(X[a][1], b), pushup(a);
      return a;
    } else {
      X[b][0] = merge(a, X[b][0]), pushup(b);
      return b;
    }
  }
  void insert(int &root, int w){
    auto [p, q] = split(root, w);
    auto [a, b] = split(p, w - 1);
    if(b != 0){
      ++ S[b], ++ C[b];
    } else b = newnode(w);
    p = merge(a, b), root = merge(p, q);
  }
  void erase(int &root, int w){
    auto [p, q] = split(root, w);
    auto [a, b] = split(p, w - 1);
    -- C[b], -- S[b];
    p  = C[b] == 0 ? a : merge(a, b);
    root = merge(p, q);
  }
  int  find_rank(int &root, int w){
    int x = root, o = x, a = 0;
    for(;x;){
      if(w <  W[x])
        o = x, x = X[x][0];
      else {
        a += S[X[x][0]];
        if(w == W[x]){ o = x; break; }
        a += C[x];
        o = x, x = X[x][1];
      }
    }
    return a + 1;
  }
  int  find_kth(int &root, int w){
    int x = root, o = x, a = 0;
    for(;x;){
      if(w <= S[X[x][0]])
        o = x, x = X[x][0];
      else {
        w -= S[X[x][0]];
        if(w <= C[x]){ o = x; break; }
        w -= C[x];
        o = x, x = X[x][1];
      } 
    }
    return W[x];
  }
  int  find_pre(int &root, int w){
return find_kth(root, find_rank(root, w) - 1);
  }
  int  find_suc(int &root, int w){
return find_kth(root, find_rank(root, w + 1));
  }
}

珂朵莉树

#include "../header.cpp"
namespace ODT {
  // <pos_type, value_type>
  map <int, long long> M;
  // 分裂为 [1, p) [p, +inf),返回后者迭代器
  auto split(int p) {
    auto it = prev(M.upper_bound(p));
    return M.insert(
      it,
      make_pair(p, it -> second)
    );
  }
  // 区间赋值
  void assign(int l, int r, int v) {
    auto it = split(l);
    split(r + 1);
    while (it -> first != r + 1) {
      it = M.erase(it);
    }
    M[l] = v;
  }
  // // 执行操作
  // void perform(int l, int r) {
  //   auto it = split(l);
  //   split(r + 1);
  //   while (it -> first != r + 1) {
  //     // Do something...
  //     it = next(it);
  //   }
  // }
};

可并堆

#include "../header.cpp"
namespace LeftHeap{
  const int SIZ = 1e5 + 3;
  int W[SIZ],D[SIZ], L[SIZ], R[SIZ], F[SIZ],s;
  bool E[SIZ];
  int merge(int u, int v){
    if(u == 0 || v == 0)
      return u | v;
    if(W[u] > W[v] || (W[u] == W[v] && u > v))
      swap(u, v);
    int &lc = L[u], &rc = R[u];
    rc = merge(rc, v);
    if(D[lc] < D[rc]) swap(lc, rc);
    D[u] = min(D[lc], D[rc]) + 1;
    if(lc != 0) F[lc] = u;
    if(rc != 0) F[rc] = u;
    return u;
  }
  void pop(int &root){
    int root0 = merge(L[root], R[root]);
    F[root0] = F[root] = root0;
    E[root] = true, root = root0;
  }
  int top(int &root){ return W[root]; }
  int getfa(int u){
    return u == F[u] ? u : F[u] = getfa(F[u]);
  }
  int newnode(int w){
    ++ s, W[s] = w, F[s] = s, D[s] = 1;
    return s;
  }
}

KD 树

#include "../header.cpp"
const int LG = 18;  // 二进制分组合并
struct pt { // x: 坐标; v: 权值; l, r: 儿子
  int x[2], v, sum, l, r, L[2], R[2];
} t[MAXN], l, h;    // l, h: 查询的左上/右下角
int rt[LG], b[MAXN], cnt;
void update(int p) {
  t[p].sum = t[t[p].l].sum + t[t[p].r].sum + t[p].v;
  for (int k: {0, 1}) {
    t[p].L[k] = t[p].R[k] = t[p].x[k];
    if(t[p].l)
t[p].L[k] = min(t[p].L[k], t[t[p].l].L[k]),
t[p].R[k] = max(t[p].R[k], t[t[p].l].R[k]);
    if(t[p].r)
t[p].L[k] = min(t[p].L[k], t[t[p].r].L[k]),
t[p].R[k] = max(t[p].R[k], t[t[p].r].R[k]);
  }
}
int build(int l, int r, int dep = 0){ // 重构
  int p{(l + r) >> 1};
  nth_element(b + l, b + p, b + r + 1, [dep](int x, int y) { return t[x].x[dep] < t[y].x[dep]; });
  int x{b[p]};
  if(l < p) t[x].l = build(l, p - 1, dep ^ 1);
  if(p < r) t[x].r = build(p + 1, r, dep ^ 1);
  update(x);
  return x;
}
void append(int &p) {   // 收集 p 子树等待重构
  if(!p) return;
  b[++cnt] = p;
  append(t[p].l), append(t[p].r), p = 0;
}
int query(int p) {      // 查询 p 子树矩阵内和
  if (!p) return 0;
  bool flag = false;
  for (int k : {0, 1}) flag |= (!(l.x[k] <= t[p].L[k] && t[p].R[k] <= h.x[k]));
  if (!flag) return t[p].sum;
  for (int k : {0, 1})
    if (t[p].R[k] < l.x[k] || h.x[k] < t[p].L[k]) return 0;
  int ans = 0;
  flag = false;
  for (int k : {0, 1}) flag |= (!(l.x[k] <= t[p].x[k] && t[p].x[k] <= h.x[k]));
  if (!flag) ans = t[p].v;
  return ans += query(t[p].l) + query(t[p].r);
}
int main() {
  int n; cin >> n; n = 0; // n: 当前 KDT 大小
  while (true) {
    int op; cin >> op;
    if (op == 1) {
      int x, y, A; cin >> x >> y >> A;
      t[++ n] = {{x, y}, A};
      b[cnt = 1] = n;
      for (int sz = 0;; ++sz) // 二进制合并
        if (!rt[sz]) {
          rt[sz] = build(1, cnt); break;
        } else append(rt[sz]);
    } else if (op == 2) {
      cin >> l.x[0] >> l.x[1] >> h.x[0] >> h.x[1];
      int ans = 0;
      for(int i = 0; i < LG; ++i) ans += query(rt[i]);
      cout << ans << "\n";
    } else break;
  }
  return 0;
}
#include "../header.cpp"
namespace LinkCutTree{
  const int SIZ = 1e5 + 3;
  int F[SIZ], C[SIZ], S[SIZ], W[SIZ], A[SIZ], X[SIZ][2], size;
  bool T[SIZ];
  bool is_root(int x){ return X[F[x]][0] != x && X[F[x]][1] != x;}
  bool is_rson(int x){ return X[F[x]][1] == x;}
  int  new_node(int w){ // 创建节点,返回编号
    ++ size;
    W[size] = w, C[size] = S[size] = 1;
    A[size] = w, F[size] = 0;
    X[size][0] = X[size][1] = 0;
    return size;
  }
  void push_up(int x){
    S[x] = C[x] + S[X[x][0]] + S[X[x][1]];
    A[x] = W[x] ^ A[X[x][0]] ^ A[X[x][1]];
  }
  void push_down(int x){
    if(!T[x]) return;
    int lc = X[x][0], rc = X[x][1];
    if(lc)T[lc] ^= 1, swap(X[lc][0],X[lc][1]);
    if(rc)T[rc] ^= 1, swap(X[rc][0],X[rc][1]);
    T[x] = false;
  }
  void update(int x){
    if(!is_root(x))update(F[x]); push_down(x);
  }
  void rotate(int x){
    int y = F[x], z = F[y];
    bool f = is_rson(x);
    bool g = is_rson(y);
    if(is_root(y)){
      F[x] = z, F[y] = x;
      X[y][ f] = X[x][!f], F[X[x][!f]] = y;
      X[x][!f] = y;
    } else {
      F[x] = z, F[y] = x;
      X[z][ g] = x;
      X[y][ f] = X[x][!f], F[X[x][!f]] = y;
      X[x][!f] = y;
    }
    push_up(y), push_up(x);
  }
  void splay(int x){  // 旋到树根
    update(x);
    for(int f = F[x];f = F[x], !is_root(x);rotate(x))
      if(!is_root(f)) rotate(is_rson(x) == is_rson(f) ? f : x);
  }
  int  access(int x){ // 将根到 x 变成实链
    int p;
    for(p = 0;x;p = x, x = F[x]){
      splay(x), X[x][1] = p, push_up(x);
    }
    return p;
  }
  void make_root(int x){  // 使 x 为所在树的根
    x = access(x);
    T[x] ^= 1, swap(X[x][0], X[x][1]);
  }
  int find_root(int x){   // 查找 x 所在树的根
    access(x), splay(x), push_down(x);
    while(X[x][0]) x = X[x][0], push_down(x);
    splay(x);
    return x;
  }
  void link(int x, int y){  // 连边
    make_root(x), splay(x), F[x] = y;
  }
  void cut(int x, int p){   // 删边
    make_root(x), access(p), splay(p), X[p][0] = F[x] = 0;
  }
  void modify(int x, int w){// 修改点权
    splay(x), W[x] = w, push_up(x);
  }
}

线段树

李超树

#include "../../header.cpp"
struct Line{ int id; double k, b; Line() = default;};
namespace LCSeg{
  const int SIZ = 2e5 + 3;
  struct Line T[SIZ];
  #define lc(t) (t << 1)
  #define rc(t) (t << 1 | 1)
  bool cmp(int p, Line x, Line y){
    double w1 = x.k * p + x.b;
    double w2 = y.k * p + y.b;
    double d = w1 - w2;
    if(fabs(d) < 1e-8) return x.id > y.id;
    return d < 0;
  }
  void merge(int t, int a, int b, Line x, Line y){
    int c = a + b >> 1;
    if(cmp(c, x, y)) swap(x, y);
    if(cmp(a, y, x)){
      T[t] = x; if(a != b) merge(rc(t), c + 1, b, T[rc(t)], y);
    } else {
      T[t] = x; if(a != b) merge(lc(t), a, c, T[lc(t)], y);
    }
  }
  // 插入线段 (l, f(l)) -- (r, f(r))
  void modify(int t, int a, int b, int l, int r, Line x){
    if(l <= a && b <= r) merge(t, a, b, T[t], x);
    else {
      int c = a + b >> 1;
      if(l <= c) modify(lc(t), a, c, l, r, x);
      if(r >  c) modify(rc(t), c + 1, b, l, r, x);
    }
  }
  // 查询 x = p 位置最高的线段(有多条取编号最小)
  void query(int t, int a, int b, int p, Line &x){
    if(cmp(p, x, T[t])) x = T[t];
    if(a != b){
      int c = a + b >> 1;
      if(p <= c) query(lc(t), a,   c, p, x);
      if(p >  c) query(rc(t), c + 1, b, p, x);
    }
  }
}

线段树 3

例题

给出一个长度为 \(n\) 的数列 \(A\),同时定义一个辅助数组 \(B\),\(B\) 开始与 \(A\) 完全相同。接下来进行了 \(m\) 次操作,操作有五种类型,按以下格式给出:

  • 1 l r k:对于所有的 \(i\in[l,r]\),将 \(A_i\) 加上 \(k\)(\(k\) 可以为负数)。
  • 2 l r v:对于所有的 \(i\in[l,r]\),将 \(A_i\) 变成 \(\min(A_i,v)\)。
  • 3 l r:求 \(\sum_{i=l}^{r}A_i\)。
  • 4 l r:对于所有的 \(i\in[l,r]\),求 \(A_i\) 的最大值。
  • 5 l r:对于所有的 \(i\in[l,r]\),求 \(B_i\) 的最大值。

在每一次操作后,我们都进行一次更新,让 \(B_i\gets\max(B_i,A_i)\)。

#include "../../header.cpp"
int A[MAXN];
struct Node{
  i64 sum; int len, max1, max2, max_cnt, his_mx;
  Node():
    sum(0), max1(-INF), max2(-INF), max_cnt(0), his_mx(-INF), len(0) {}
  Node(int w):
    sum(w), max1(   w), max2(-INF), max_cnt(1), his_mx(   w), len(1) {}
  bool update(int w1, int w2, int h1, int h2){
    his_mx = max({his_mx, max1 + h1});
    max1 += w1, max2 += w2;
    sum += 1ll * w1 * max_cnt + 1ll * w2 * (len - max_cnt);
    return max1 > max2;
  }
};
struct Tag{
  int max_add, max_his_add, umx_add, umx_his_add; bool have;
  void update(int w1, int w2, int h1, int h2){
    max_his_add = max(max_his_add, max_add + h1);
    umx_his_add = max(umx_his_add, umx_add + h2);
    max_add += w1, umx_add += w2, have = true;
  }
  void clear(){
    max_add = max_his_add = umx_add = umx_his_add = have = 0;
  }
};
struct Node operator +(Node a, Node b){
  Node t;
  t.max1 = max(a.max1, b.max1);
  if(t.max1 != a.max1){
    if(a.max1 > t.max2) t.max2 = a.max1;
  } else{
    if(a.max2 > t.max2) t.max2 = a.max2;
    t.max_cnt += a.max_cnt;
  }
  if(t.max1 != b.max1){
    if(b.max1 > t.max2) t.max2 = b.max1;
  } else{
    if(b.max2 > t.max2) t.max2 = b.max2;
    t.max_cnt += b.max_cnt;
  }
  t.sum = a.sum + b.sum, t.len = a.len + b.len;
  t.his_mx = max(a.his_mx, b.his_mx);
  return t;
}
namespace Seg{
  const int SIZ = 2e6 + 3;
  struct Node W[SIZ]; struct Tag T[SIZ];
  #define lc(t) (t << 1)
  #define rc(t) (t << 1 | 1)
  void push_up(int t, int a, int b){
    W[t] = W[lc(t)] + W[rc(t)];
  }
  void push_down(int t, int a, int b){
    if(a == b) T[t].clear();
    if(T[t].have){
      int c = a + b >> 1, x = lc(t), y = rc(t);
      int w = max(W[x].max1, W[y].max1);
      int w1 = T[t].max_add, w2 = T[t].umx_add, w3 = T[t].max_his_add, w4 = T[t].umx_his_add;
      if(w == W[x].max1)
        W[x].update(w1, w2, w3, w4),
        T[x].update(w1, w2, w3, w4);
      else 
        W[x].update(w2, w2, w4, w4),
        T[x].update(w2, w2, w4, w4);
      if(w == W[y].max1)
        W[y].update(w1, w2, w3, w4),
        T[y].update(w1, w2, w3, w4);
      else 
        W[y].update(w2, w2, w4, w4),
        T[y].update(w2, w2, w4, w4);
      T[t].clear();
    }
  }
  void build(int t, int a, int b){
    if(a == b){W[t] = Node(A[a]), T[t].clear();} else {
      int c = a + b >> 1; T[t].clear();
      build(lc(t), a,   c);
      build(rc(t), c + 1, b);
      push_up(t, a, b);
    }
  }
  void modiadd(int t, int a, int b, int l, int r, int w){
    if(l <= a && b <= r){
      T[t].update(w, w, w, w);
      W[t].update(w, w, w, w);
    } else {
      int c = a + b >> 1; push_down(t, a, b);
      if(l <= c) modiadd(lc(t), a,   c, l, r, w);
      if(r >  c) modiadd(rc(t), c + 1, b, l, r, w);
      push_up(t, a, b);
    }
  }
  void modimin(int t, int a, int b, int l, int r, int w){
    if(l <= a && b <= r){
      if(w >= W[t].max1) return; else 
      if(w >  W[t].max2){
        int k = w - W[t].max1;
        T[t].update(k, 0, k, 0);
        W[t].update(k, 0, k, 0);
      } else {
        int c = a + b >> 1;
        push_down(t, a, b);
        modimin(lc(t), a,   c, l, r, w);
        modimin(rc(t), c + 1, b, l, r, w);
        push_up(t, a, b);
      }
    } else {
      int c = a + b >> 1; push_down(t, a, b);
      if(l <= c) modimin(lc(t), a,   c, l, r, w);
      if(r >  c) modimin(rc(t), c + 1, b, l, r, w);
      push_up(t, a, b);
    }
  }
  Node query(int t, int a, int b, int l, int r){
    if(l <= a && b <= r) return W[t];
    int c = a + b >> 1; Node ret; push_down(t, a, b);
    if(l <= c) ret = ret + query(lc(t), a,   c, l, r);
    if(r >  c) ret = ret + query(rc(t), c + 1, b, l, r);
    return ret;
  }
}
int qread();
int main(){  
  int n = qread(), m = qread();
  for(int i = 1;i <= n;++ i)
    A[i] = qread();
  Seg :: build(1, 1, n);
  for(int i = 1;i <= m;++ i){
    int op = qread();
    if(op == 1){
      int l = qread(), r = qread(), w = qread();
      Seg :: modiadd(1, 1, n, l, r, w);
    } else if(op == 2){
      int l = qread(), r = qread(), w = qread();
      Seg :: modimin(1, 1, n, l, r, w);
    } else if(op == 3){
      int l = qread(), r = qread();
      auto p = Seg :: query(1, 1, n, l, r);
      printf("%lld\n", p.sum);
    } else if(op == 4){
      int l = qread(), r = qread();
      auto p = Seg :: query(1, 1, n, l, r);
      printf("%d\n", p.max1);
    } else if(op == 5){
      int l = qread(), r = qread();
      auto p = Seg :: query(1, 1, n, l, r);
      printf("%d\n", p.his_mx);
    }
  }
  return 0;
}

根号数据结构

树论

点分树

例题

给定 \(n\) 个点组成的树,点有点权 \(v_i\)。\(m\) 个操作,分为两种:

  • 0 x k 查询距离 \(x\) 不超过 \(k\) 的所有点的点权之和;
  • 0 x y 将点 \(x\) 的点权修改为 \(y\)。
#include "../header.cpp"
vector<int> E[MAXN];
namespace LCA{
  const int MAXH = 18 + 3;
  int D[MAXN], F[MAXN];
  int P[MAXN], Q[MAXN], o, h = 18;
  void dfs(int u, int f){
    ++ o;
    P[u] = o, Q[o] = u;
    F[u] = f, D[u] = D[f] + 1;
    for(auto &v : E[u]) if(v != f)
      dfs(v, u);
  }
  int ST[MAXN][MAXH];
  int cmp(int a, int b){
    return D[a] < D[b] ? a : b;
  }
  int T[MAXN], n;
  void init(int _n);  // 初始化 ST 表
  int lca(int a, int b){
    if(a == b) return a;
    int l = P[a], r = P[b];
    if(l > r) swap(l, r);
    ++ l;
    int d = T[r - l + 1];
    return F[cmp(ST[l][d], ST[r - (1 << d) + 1][d])];
  }
  int dis(int a, int b);
}
namespace BIT{
  void add(int D[], int n, int p, int w){
    ++ p;
    while(p <= n) D[p] += w, p += p & -p;
  }
  int pre(int D[], int n, int p){
    if(p < 0) return 0;
    p = min(n, p + 1);
    int r = 0;
    while(p >  0) r += D[p], p -= p & -p;
    return r;
  }
}
namespace PTree{
  vector<int> EE[MAXN];
  bool V[MAXN];
  int S[MAXN], L[MAXN], *D1[MAXN], *D2[MAXN];
  using LCA :: dis, BIT :: add, BIT :: pre;
  void dfs1(int s, int &g, int u, int f){
    S[u] = 1;
    int maxsize = 0;
    for(auto &v : E[u]) if(v != f && !V[v]){
      dfs1(s, g, v, u);
      maxsize = max(maxsize, S[v]);
      S[u] += S[v];
    }
    maxsize = max(maxsize, s - S[u]);
    if(maxsize <= s / 2) g = u;
  }
  int n;
  void build(int s, int &g, int u, int f){
    dfs1(s, g, u, f);
    V[g] = true, L[g] = s;
    for(auto &u : E[g]) if(!V[u]){
      int h = 0;
      if(S[u] < S[g]) build(S[u], h, u, 0);
      else        build(s - S[g], h, u, 0);
      EE[g].push_back(h);
      EE[h].push_back(g);
    }
  }
  int F[MAXN];
  void dfs2(int u, int f){
    F[u] = f;
    for(auto &v : EE[u]) if(v != f){
      dfs2(v, u);
    }
  }
  void build(int _n){   // 建树(需初始化 LCA)
    n = _n;
    int s = n, g = 0;
    dfs1(s, g, 1, 0);
    V[g] = true, L[g] = s;
    for(auto &u : E[g]){
      int h = 0;
      if(S[u] < S[g]) build(S[u], h, u, 0);
      else build(s - S[g], h, u, 0);
      EE[g].push_back(h);
      EE[h].push_back(g);
    }
    dfs2(g, 0);
    for(int i = 1;i <= n;++ i){
      L[i] += 2;
      D1[i] = new int[L[i] + 3];
      D2[i] = new int[L[i] + 3];
      for(int j = 0;j < L[i] + 3;++ j)
        D1[i][j] = D2[i][j] = 0;
    }
  }
  void modify(int x, int w){  // 修改点权
    int u = x;
    while(1){
      add(D1[x], L[x], dis(u, x), w);
      int y = F[x];
      if(y != 0){
        int e = LCA :: dis(x, y);
        add(D2[x], L[x], dis(u, y), w);
        x = y;
      } else break;
    }
  }
  int query(int x, int d){
    int ans = 0, u = x;
    while(1){
      ans += pre(D1[x], L[x], d - dis(u, x));
      int y = F[x];
      if(y != 0){
        int e = dis(x, y);
        ans-= pre(D2[x], L[x], d - dis(u, y));
        x = y;
      } else break;
    }
    return ans;
  }
}

树哈希

用法

有根树求出每个子树的哈希值,儿子间顺序可交换。

#include "../header.cpp"
u64 xor_shift(u64 x);
u64 H[MAXN];
vector <int> E[MAXN];
void dfs(int u, int f){
  H[u] = 1;
  for(auto &v: E[u]) if(v != f)
    dfs(v, u), H[u] += H[v];
  H[u] = xor_shift(H[u]); // !important
}

Prufer 序列

#include "../header.cpp"
int D[MAXN], F[MAXN], P[MAXN];
vector<int> tree2prufer(int n){
  vector <int> P(n);
  for(int i = 1, j = 1;i <= n - 2;++ i, ++ j){
    while(D[j]) ++ j;
    P[i] = F[j];
    while(i<= n - 2 && !--D[P[i]] && P[i] < j)
      P[i + 1] = F[P[i]], i ++;
  }
  return P;
}
vector<int> prufer2tree(int n){
  vector <int> F(n);
  for(int i = 1, j = 1;i <= n - 1;++ i, ++ j){
    while(D[j]) ++ j;
    F[j] = P[i];
    while(i<= n - 1 && !--D[P[i]] && P[i] < j)
      F[P[i]] = P[i + 1], i ++;
  }
  return F;
}

虚树

#include "../header.cpp"
vector<pair<int, int> > E[MAXN];
namespace LCA{
  const int SIZ = 5e5 + 3;
  int D[SIZ], H[SIZ], F[SIZ], P[SIZ], Q[SIZ], o;
  void dfs(int u, int f){
    P[u] = ++ o, Q[o] = u, F[u] = f, D[u] = D[f] + 1;
    for(auto &[v, w] : E[u]) if(v != f){
      H[v] = H[u] + w, dfs(v, u);
    }
  }
  const int MAXH = 18 + 3;
  int h = 18;
  int ST[SIZ][MAXH];
  int cmp(int a, int b){
    return D[a] < D[b] ? a : b;
  }
  int T[SIZ], n;
  void init(int _n, int root);
  int lca(int a, int b);
  int dis(int a, int b);
}
bool cmp(int a, int b){
  return LCA :: P[a] < LCA :: P[b];
}
bool I[MAXN];
vector <int> E1[MAXN], V1;
void solve(vector <int> &V){
  using LCA :: lca; using LCA :: D;
  stack <int> S;
  sort(V.begin(), V.end(), cmp);
  S.push(1);
  int v, l;
  for(auto &u : V) I[u] = true;
  for(auto &u : V) if(u != 1){
    int f = lca(u, S.top());
    l = -1;
    while(D[v = S.top()] > D[f]){
      if(l != -1)
        E1[v].push_back(l);
      V1.push_back(l = v), S.pop();
    }
    if(l != -1)
      E1[f].push_back(l);
    if(f != S.top()) S.push(f);
    S.push(u);
  }
  l = -1;
  while(!S.empty()){
    v = S.top();
    if(l != -1) E1[v].push_back(l);
    V1.push_back(l = v), S.pop();
  }
  // dfs(1, 0); // SOLVE HERE !!!
  for(auto &u : V1)
    E1[u].clear(), I[u] = false;
  V1.clear();
}

图论

三元环计数

无向图:考虑将所有点按度数从小往大排序,然后将每条边定向,由排在前面的指向排在后面的,得到一个有向图。然后考虑枚举一个点,再枚举一个点,暴力数,具体见代码。结论是,这样定向后,每个点的出度是 \(O(\sqrt{m})\) 的。复杂度 \(O(m\sqrt{m})\)。 有向图:不难发现,上述方法枚举了三个点,计算有向图三元环也就只需要处理下方向的事,这个由于算法够暴力,随便改改就能做了。

#include "../header.cpp"
// 无向图
ll solve1(){
    ll n, m; cin >> n >> m;
    vector<pair<ll, ll>> Edges(m);
    vector<vector<ll>> G(n + 2);
    vector<ll> deg(n + 2);
    for (auto &[i, j] : Edges)
        cin >> i >> j, ++deg[i], ++deg[j];
    for (auto [i, j] : Edges) {
        if (deg[i] > deg[j] || (deg[i] == deg[j] && i > j)) swap(i, j);
        G[i].emplace_back(j);
    }
    vector<ll> val(n + 2);
    ll ans = 0;
    for (ll i = 1; i <= n; ++i) {
        for (auto j : G[i]) ++val[j];
        for (auto j : G[i])
      for (auto k : G[j]) ans += val[k];
        for (auto j : G[i]) val[j] = 0;
    }
    return ans;
}
// 有向图
ll solve2(){
    ll n, m; cin >> n >> m;
    vector<pair<ll, ll>> Edges(m);
    vector<vector<pair<ll, ll>>> G(n + 2);
    vector<ll> deg(n + 2);
    for (auto &[i, j] : Edges)
    cin >> i >> j, ++deg[i], ++deg[j];
    for (auto [i, j] : Edges) {
        ll flg = 0;
        if (deg[i] > deg[j] || (deg[i] == deg[j] && i > j)) swap(i, j), flg = 1;
        G[i].emplace_back(j, flg);
    }
    vector<ll> in(n + 2), out(n + 2);
    ll ans = 0;
    for (ll i = 1; i <= n; ++i) {
        for(auto [j, w] : G[i])
      w ? (++in[j]) : (++out[j]);
        for(auto [j, w1] : G[i])
      for (auto [k, w2] : G[j]) {
            if (w1 == w2)ans += w1 ? in[k] : out[k];
        }
        for(auto [j, w] : G[i])in[j] = out[j] = 0;
    }
    return ans;
}

四元环计数

  • 无向图:类似,由于定向后出度结论过于强大,可以暴力。讨论了三种情况。
  • 有向图:缺少题目,但应当类似三元环计数有向形式记录定向边和原边的正反关系。因为此法最强的结论是定向后出度 \(O(\sqrt{m})\),实际上方法很暴力,应当不难数有向形式的。
#include "../header.cpp"
ll solve(){
  ll n, m; cin >> n >> m;
  vector<pair<ll, ll>> Edges(m);
  vector<vector<ll>> G(n + 2), iG(n + 2);
  vector<ll> deg(n + 2);
  for (auto &[i, j] : Edges)
    cin >> i >> j, ++deg[i], ++deg[j];
  for (auto [i, j] : Edges) {
    if (deg[i] > deg[j] || (deg[i] == deg[j] && i > j)) swap(i, j);
    G[i].emplace_back(j), iG[j].emplace_back(i);
  }
  ll ans = 0;
  vector<ll> v1(n + 2), v2(n + 2);
  for (ll i = 1; i <= n; ++i) {
    for (auto j : G[i])
      for (auto k : G[j]) ++v1[k];
    for (auto j : iG[i])
      for (auto k : G[j])
        ans += v1[k], ++v2[k];
    for (auto j : G[i])
      for (auto k : G[j])
        ans += v1[k] * (v1[k] - 1) / 2, v1[k] = 0;
    for (auto j : iG[i]) for (auto k : G[j]) {
      if (deg[k] > deg[i] || (deg[k] == deg[i] && k > i)) ans += v2[k] * (v2[k] - 1) / 2;
      v2[k] = 0;
    }
  }
  return ans;
}

支配树

// From Alex_Wei
#include "../header.cpp"
int n, m, dn, F[MAXN], ind[MAXN], dfn[MAXN];
int sdom[MAXN], idom[MAXN], sz[MAXN];
vector<int> E[MAXN], rE[MAXN], buc[MAXN];
void dfs(int u, int f) {
  ind[dfn[u] = ++dn] = u, F[u] = f;
  for(auto &v: E[u]) if(!dfn[v]) dfs(v, u);
}
struct dsu {
  // M 维护 sdom 最小的点的编号
  int F[MAXN], M[MAXN];
  int find(int x) {
    if(F[x] == x) return F[x];
    int f = F[x];
    F[x] = find(f);
    if(sdom[M[f]] < sdom[M[x]]) M[x] = M[f];
    return F[x];
  }
  int get(int x) { return find(x), M[x]; }
} tr;
int main() {
  cin >> n >> m;
  for(int i = 1; i <= m; i++) {
    int u, v; cin >> u >> v;
    E[u].push_back(v), rE[v].push_back(u);
  }
  dfs(1, 0), sdom[0] = n + 1;
  for(int i = 1; i <= n; i++) tr.F[i] = i;
  for(int i = n; i; -- i){
    int u = ind[i];
    for(auto &v: buc[i]) idom[v] = tr.get(v);
    if(i == 1) break;
    sdom[u] = i;
    for(auto &v: rE[u]) {
      sdom[u] = min(sdom[u], dfn[v] <= i ? dfn[v] : sdom[tr.get(v)]);
    }
    tr.M[u] = u, tr.F[u] = F[u];
    buc[sdom[u]].push_back(u);
  }
  for(int i = 2; i <= n; i++) {
    int u = ind[i];
    if(sdom[idom[u]] != sdom[u])
        idom[u] = idom[idom[u]];
    else idom[u] = sdom[u];
  }
  for(int i = n;i;i --){
    sz[i] += 1;
    if(i > 1) sz[ind[idom[i]]] += sz[i];
  }
  for(int i = 1;i <= n;++ i){
    cout << sz[i] << " \n"[i == n];
  }
  return 0;
}

二分图最大匹配

#include "../header.cpp"
vector <int> G[MAXN];
bool V[MAXN];
int ML[MAXN], MR[MAXN];
bool kuhn(int u){
    V[u] = true;
    for(auto &v: G[u]) if(MR[v] == 0){
        ML[u] = v, MR[v] = u;
        return true;
    }
    for(auto &v: G[u]) if(!V[MR[v]] && kuhn(MR[v])){
        ML[u] = v, MR[v] = u;
        return true;
    }
    return false;
}
void solve(int L, int R){
    for(int i = 1;i <= L;++ i){
        shuffle(G[i].begin(), G[i].end(), MT);
    } // 需要打乱避免构造
    while(1){
        bool ok = false;
        memset(V, false, sizeof(V));
        for(int i = 1;i <= L;++ i)
            ok |= !ML[i] && kuhn(i);
        if(!ok) break;
    }
}

一般图最大匹配

#include "../header.cpp"
int n;
vector <int> E[MAXN];
queue  <int> Q;
int vis[MAXN], F[MAXN], col[MAXN], pre[MAXN], mat[MAXN], tmp;
int getfa(int x){
  return x == F[x] ? x : F[x] = getfa(F[x]);
}
int lca(int x, int y){
  for(++ tmp;;x = pre[mat[x]], swap(x, y)) 
    if(vis[x = getfa(x)] == tmp) return x; 
      else vis[x] = x ? tmp : 0;
}
void flower(int x, int y, int z){
  while(getfa(x) != z){
    pre[x] = y, y = mat[x], F[x] = F[y] = z;
    x = pre[y];
    if(col[y] == 2)
      Q.push(y), col[y] = 1;
  }
}
bool aug(int u){
  for(int i = 1;i <= n;++ i)
    col[i] = pre[i] = 0, F[i] = i;
  Q = queue<int>({ u }), col[u] = 1;
  while(!Q.empty()){
    auto x = Q.front(); Q.pop();
    for(auto &v: E[x]){
      int y = v, z;
      if(col[y] == 2) continue;
      if(col[y] == 1) {
        z = lca(x, y);
        flower(x, y, z), flower(y, x, z);
      } else 
      if(!mat[y]){
        for(pre[y] = x; y;){
          mat[y] = x = pre[y], swap(y,mat[x]);
        }
        return true;
      } else {
        pre[y] = x, col[y] = 2;
        Q.push(mat[y]), col[mat[y]] = 1;
      }
    }
  }
  return false;
}

2-SAT

例题

\(n\) 个变量 \(m\) 个条件,形如若 \(x_i = a\) 则 \(y_j = b\),找到任意一组可行解或者报告无解。

#include "tarjan-scc.cpp"
const int MAXN = 1e6 + 3;
int X[MAXN][2], o;
int main(){
  ios :: sync_with_stdio(false);
  int n, m;
  cin >> n >> m;
  for(int i = 1;i <= n;++ i)
    X[i][0] = ++ o, X[i][1] = ++ o;
  for(int i = 1;i <= m;++ i){
    int a, x, b, y;
    cin >> a >> x >> b >> y;
    SCC :: add(X[a][!x], X[b][y]);
    SCC :: add(X[b][!y], X[a][x]);
  }
  for(int i = 1;i <= o;++ i)
    if(!SCC :: F[i])
      SCC :: dfs(i);
  bool ok = true;
  for(int i = 1;i <= n;++ i){
    if(SCC :: C[X[i][0]] == SCC :: C[X[i][1]])
      ok = false;
  }
  if(ok){
    cout << "POSSIBLE" << endl;
    for(int i = 1;i <= n;++ i){
      int a = SCC :: C[X[i][0]];
      int b = SCC :: C[X[i][1]];
      cout << (a >= b) << " ";
    }
    cout << endl;
  } else {
    cout << "IMPOSSIBLE" << endl;
  }
  return 0;
}

割点

#include "../header.cpp"
vector<int> V[MAXN];
int n, m, o, D[MAXN], L[MAXN];
bool F[MAXN], C[MAXN];
// 对每个连通块调用 dfs(i, i)
void dfs(int u, int g){
  L[u] = D[u] = ++ o, F[u] = true; int s = 0;
  for(auto &v : V[u]){
    if(!F[v]){
      dfs(v, g), ++ s;
      L[u] = min(L[u], L[v]);
      if(u != g && L[v] >= D[u]) C[u] = true;
    } else {
      L[u] = min(L[u], D[v]);
    }
  }
  // C[u] 为真表示该点是割点
  if(u == g && s > 1) C[u] = true;
}

边双连通分量

#include "../header.cpp"
vector <vector<int>> A;
vector <pair<int, int>> V[MAXN];
stack  <int> S;
int D[MAXN], L[MAXN], o; bool I[MAXN];
void dfs(int u, int l){
  D[u] = L[u] = ++ o; I[u] = true, S.push(u);
  int s = 0;
  for(auto &[v, g] : V[u]) if(g != l) {
    if(D[v]){
      if(I[v])    L[u] = min(L[u], D[v]);
    } else {
      dfs(v, g), L[u] = min(L[u], L[v]), ++ s;
    }
  }
  if(D[u] == L[u]){
    vector <int> T;
    while(S.top() != u){
      int v = S.top(); S.pop();
      T.push_back(v), I[v] = false;
    }
    T.push_back(u), S.pop(), I[u] = false;
    A.push_back(T);
  }
}

点双连通分量

#include "../header.cpp"
vector <vector<int>> A;
vector <int> V[MAXN];
stack  <int> S;
int D[MAXN], L[MAXN], o; bool I[MAXN];
void dfs(int u, int f){
  D[u] = L[u] = ++ o; I[u] = true, S.push(u);
  int s = 0;
  for(auto &v : V[u]) if(v != f){
    if(D[v]){
      if(I[v])   L[u] = min(L[u], D[v]);
    } else {
      dfs(v, u), L[u] = min(L[u], L[v]), ++ s;
      if(L[v] >= D[u]){
        vector <int> T;
        while(S.top() != v){
          int t = S.top(); S.pop();
          T.push_back(t), I[t] = false;
        }
        T.push_back(v), S.pop(), I[v] = false;
        T.push_back(u);
        A.push_back(T);
      }
    }
  }
  if(f == 0 && s == 0)
    A.push_back({u}); // 孤立点特判
}

强连通分量

#include "../header.cpp"
namespace SCC {
  vector <int> V[MAXN];
  stack  <int> S;
  int D[MAXN], L[MAXN], C[MAXN], o, s;
  bool F[MAXN], I[MAXN];
  void add(int u, int v){ V[u].push_back(v); }
  void dfs(int u){
    L[u] = D[u] = ++ o, S.push(u), I[u] = F[u] = true;
    for(auto &v : V[u]){
      if(F[v]){
        if(I[v]) L[u] = min(L[u], D[v]);
      } else {
        dfs(v),  L[u] = min(L[u], L[v]);
      }
    }
    if(L[u] == D[u]){
      int c = ++ s;
      while(S.top() != u){
        int v = S.top(); S.pop();
        I[v] = false;
        C[v] = c;
      }
      S.pop(), I[u] = false, C[u] = c;
    }
  }
}

网络流

费用流

#include "../header.cpp"
namespace MCMF{
  int H[MAXN], V[MAXM], N[MAXM], W[MAXM], F[MAXM], o = 1, n;
  void add(int u, int v, int f, int c){
    V[++ o] = v, N[o] = H[u], H[u] = o, F[o] = f, W[o] =  c;
    V[++ o] = u, N[o] = H[v], H[v] = o, F[o] = 0, W[o] = -c;
    n = max({n, u, v});
  }
  void clear(){
    for(int i = 1;i <= n;++ i) H[i] = 0;
    n = 0, o = 1;
  }
  bool I[MAXN]; i64 D[MAXN];
  bool spfa(int s, int t){
    queue <int> Q;
    Q.push(s), I[s] = true;
    for(int i = 1;i <= n;++ i)
      D[i] = INFL;
    D[s] = 0;
    while(!Q.empty()){
      int u = Q.front();Q.pop(), I[u] = false;
      for(int i = H[u];i;i = N[i]){
        int &v = V[i], &f = F[i], &w = W[i];
        if(f && D[u] + w < D[v]){
          D[v] = D[u] + w;
          if(!I[v]) Q.push(v), I[v] = true;
        }
      }
    }
    return D[t] != INFL;
  }
  int C[MAXN]; bool T[MAXN];
  pair<i64, i64> dfs(int s, int t, int u, i64 maxf){
    if(u == t)
      return make_pair(maxf, 0);
    i64 totf = 0, totc = 0;
    T[u] = true;
    for(int &i = C[u];i;i = N[i]){
      int &v = V[i], &f = F[i], &w = W[i];
      if(f && D[v] == D[u] + w && !T[v]){
        auto [f, c] = dfs(s, t, v, min(1ll * F[i], maxf));
        F[i  ] -= f, F[i ^ 1] += f;
        totf += f, maxf -= f;
        totc += 1ll * f * W[i] + c;
        if(maxf == 0){
          T[u] = false;
          return make_pair(totf, totc);
        }
      }
    }
    T[u] = false;
    return make_pair(totf, totc);
  }
  pair<i64, i64> mcmf(int s, int t){
    i64 ans1 = 0, ans2 = 0;
    while(spfa(s, t)){
      memcpy(C, H, sizeof(int) * (n + 3));
      auto [f, c] = dfs(s, t, s, INFL);
      ans1 += f, ans2 += c;
    }
    return make_pair(ans1, ans2);
  }
}

最小割树

用法

给定无向图求出最小割树,点 \(u\) 和 \(v\) 作为起点终点的最小割为树上 \(u\) 到 \(v\) 路径上边权的最小值。

#include "../header.cpp"
namespace Dinic{
  const i64 INF = 1e18;
  const int SIZ = 1e5 + 3;
  int n, m;
  int H[SIZ], V[SIZ], N[SIZ], F[SIZ], t = 1;
  int add(int u, int v, int f){
    V[++ t] = v, N[t] = H[u], F[t] = f, H[u] = t;
    V[++ t] = u, N[t] = H[v], F[t] = 0, H[v] = t;
    n = max(n, u);
    n = max(n, v);
    return t - 1;
  }
  void clear(){
    for(int i = 1;i <= n;++ i) H[i] = 0;
    n = m = 0, t = 1;
  }
  int D[SIZ];
  bool bfs(int s, int t){
    queue <int> Q;
    for(int i = 1;i <= n;++ i) D[i] = 0;
    Q.push(s), D[s] = 1;
    while(!Q.empty()){
      int u = Q.front(); Q.pop();
      for(int i = H[u];i;i = N[i]){
        const int &v = V[i], &f = F[i];
        if(f != 0 && !D[v])
          D[v] = D[u] + 1, Q.push(v);
      }
    }
    return D[t] != 0;
  }
  int C[SIZ];
  i64 dfs(int s, int t, int u, i64 maxf){
    if(u == t)
      return maxf;
    i64 totf = 0;
    for(int &i = C[u];i;i = N[i]){
      const int &v = V[i];
      const int &f = F[i];
      if(D[v] == D[u] + 1){
        i64 ff = dfs(s, t, v, min(maxf, 1ll * f));
        totf += ff, maxf -= ff;
        F[i] -= ff, F[i ^ 1] += ff;
        if(maxf == 0) return totf;
      }
    }
    return totf;
  }
  i64 dinic(int s, int t){
    i64 ans = 0;
    while(bfs(s, t)){
      memcpy(C, H, sizeof(int) * (n + 3));
      ans += dfs(s, t, s, INF);
    }
    return ans;
  }
}
namespace GHTree{
  const int INF  = 1e9;
  int n, m, U[MAXM], V[MAXM], W[MAXM], A[MAXM], B[MAXM];
  void add(int u, int v, int w){
    ++ m;
    U[m] = u, V[m] = v, W[m] = w;
    A[m] = Dinic :: add(u, v, w);
    B[m] = Dinic :: add(v, u, w);
    n = max({n, u, v});
  }
  vector <pair<int, int> > E[MAXN];
  void build(vector <int> N){
    int s = N.front(), t = N.back();
    if(s == t) return;
    for(int i = 1;i <= m;++ i){
      int a = A[i]; Dinic :: F[a] = W[i], Dinic :: F[a ^ 1] = 0;
      int b = B[i]; Dinic :: F[b] = W[i], Dinic :: F[b ^ 1] = 0;
    }
    int w = Dinic :: dinic(s, t);
    E[s].push_back(make_pair(t, w));
    E[t].push_back(make_pair(s, w));
    vector <int> P, Q;
    for(auto &u : N){
      if(Dinic :: D[u] != 0)
        P.push_back(u);
      else
        Q.push_back(u);
    }
    build(P), build(Q);
  }
  int D[MAXN];
  int cut(int s, int t){
    queue <int> Q; Q.push(s);
    for(int i = 1;i <= n;++ i)
      D[i] = -1;
    D[s] = INF;
    while(!Q.empty()){
      int u = Q.front(); Q.pop();
      for(auto &[v, w] : E[u]){
        if(D[v] == -1){
          D[v] = min(D[u], w);
          Q.push(v);
        }
      }
    }
    return D[t];
  }
}

最大流

#include "../header.cpp"
namespace Dinic{
  const i64 INF = 1e18;
  int n, H[MAXN], V[MAXM], N[MAXM], F[MAXM], t = 1;
  void add(int u, int v, int f){
    V[++ t] = v, N[t] = H[u], F[t] = f, H[u] = t;
    V[++ t] = u, N[t] = H[v], F[t] = 0, H[v] = t;
    n = max({n, u, v});
  }
  void clear(){
    for(int i = 1;i <= n;++ i)
      H[i] = 0;
    n = 0, t = 1;
  }
  i64 D[MAXN];
  bool bfs(int s, int t){
    queue <int> Q;
    for(int i = 1;i <= n;++ i) D[i] = 0;
    Q.push(s), D[s] = 1;
    while(!Q.empty()){
      int u = Q.front(); Q.pop();
      for(int i = H[u];i;i = N[i]){
        const int &v = V[i], &f = F[i];
        if(f != 0 && !D[v])
          D[v] = D[u] + 1, Q.push(v);
      }
    }
    return D[t] != 0;
  }
  int C[MAXN];
  i64 dfs(int s, int t, int u, i64 maxf){
    if(u == t) return maxf;
    i64 totf = 0;
    for(int &i = C[u];i;i = N[i]){
      const int &v = V[i], &f = F[i];
      if(f && D[v] == D[u] + 1){
        i64 f = dfs(s, t, v, min(1ll * f, maxf));
        F[i] -= f, F[i ^ 1] += f, totf += f, maxf -= f;
        if(maxf == 0)
          return totf;
      }
    }
    return totf;
  }
  i64 dinic(int s, int t){
    i64 ans = 0;
    while(bfs(s, t)){
      memcpy(C, H, sizeof(int) * (n + 3));
      ans += dfs(s, t, s, INFL);
    }
    return ans;
  }
}

上下界费用流

用法

  • add(u, v, l, r, c):连一条容量在 \([l, r]\) 的从 \(u\) 到 \(v\) 的费用为 \(c\) 的边;
  • solve():计算无源汇最小费用可行流;
  • solve(s, t):计算有源汇最小费用最大流。
#define add add0
#include "flow-cost.cpp"
#undef add
namespace MCMF{
  i64 cost0; int G[MAXN];
  void add(int u, int v, int l, int r, int c){
    G[v] += l, G[u] -= l;
    cost0 += 1ll * l * c;
    add0(u, v, r - l, c);
  }
  i64 solve(){
    int s = ++ n, t = ++ n;
    i64 sum = 0;
    for(int i = 1;i <= n - 2;++ i){
      if(G[i] < 0)
        add0(i, t, -G[i], 0);
      else
        add0(s, i,  G[i], 0), sum += G[i];
    }
    auto res = mcmf(s, t);
    if(res.first != sum)
      return -1;
    return res.second + cost0;
  }
  i64 solve(int s0, int t0){
    add0(t0, s0, INF, 0);
    int s = ++ n;
    int t = ++ n;
    i64 sum = 0;
    for(int i = 1;i <= n - 2;++ i){
      if(G[i] < 0)
        add0(i, t, -G[i], 0);
      else
        add0(s, i,  G[i], 0), sum += G[i];
    }
    auto res = mcmf(s, t);
    if(res.first != sum)
      return -1;
    return res.second + cost0;
  }
}

上下界最大流

用法

  • add(u, v, l, r, c):连一条容量在 \([l, r]\) 的从 \(u\) 到 \(v\) 的边;
  • solve():检查是否存在无源汇可行流;
  • solve(s, t):计算有源汇最大流。
#define add add0
#include "flow-max.cpp"
#undef add
namespace Dinic{
  int G[MAXN];
  void add(int u, int v, int l, int r){
    G[v] += l, G[u] -= l;
    add0(u, v, r - l);
  }
  void clear(){
    for(int i = 1;i <= t;++ i){
      N[i] = F[i] = V[i] = 0;
    }
    for(int i = 1;i <= n;++ i){
      H[i] = G[i] = C[i] = 0;
    }
    t = 1, n = 0;
  }
  bool solve(){ // 为真表示有解
    int s = ++ n, t = ++ n;
    i64 sum = 0;
    for(int i = 1;i <= n - 2;++ i){
      if(G[i] < 0)
        add0(i, t, -G[i]);
      else
        add0(s, i,  G[i]), sum += G[i];
    }
    return sum != dinic(s, t);
  }
  i64 solve(int s0, int t0){
    add0(t0, s0, INF);
    int s = ++ n, t = ++ n;
    i64 sum = 0;
    for(int i = 1;i <= n - 2;++ i){
      if(G[i] < 0)
        add0(i, t, -G[i]);
      else
        add0(s, i,  G[i]), sum += G[i];
    }
    return dinic(s, t) == sum ? dinic(s0, t0) : -1;
  }
}

数学

线性代数

行列式

#include "../../header.cpp"
struct Mat{
  int n, m, W[MAXN][MAXN];
  Mat(int _n = 0, int _m = 0){
    n = _n, m = _m;
    for(int i = 1;i <= n;++ i)
      for(int j = 1;j <= m;++ j)
        W[i][j] = 0;
  }
};
int mat_det(Mat a){
  int ans = 1;
  const int &n = a.n;
  for(int i = 1;i <= n;++ i){
    int f = -1;
    for(int j = i;j <= n;++ j) if(a.W[j][i]){
      f = j; break;
    }
    if(f == -1) return 0;
    if(f != i){
      for(int j = 1;j <= n;++ j)
        swap(a.W[i][j], a.W[f][j]);
      ans = MOD - ans;
    }
    for(int j = i + 1;j <= n;++ j) if(a.W[j][i]){
      while(a.W[j][i]){
        int u = a.W[i][i], v = a.W[j][i];
        if(u > v){
          for(int k = 1;k <= n;++ k)
            swap(a.W[i][k], a.W[j][k]);
          ans = MOD - ans, swap(u, v);
        }
        int rate = v / u;
        for(int k = 1;k <= n;++ k){
          a.W[j][k] = (a.W[j][k] - 1ll * rate * a.W[i][k] % MOD + MOD) % MOD;
        }
      }
    }
  }
  for(int i = 1;i <= n;++ i)
    ans = 1ll * ans * a.W[i][i] % MOD;
  return ans;
}

大步小步

用法

给定 \(a, p\) 求出 \(x\) 使得 \(a^x = y \pmod p\),其中 \(p\) 为质数。

#include "../header.cpp"
namespace BSGS {
  unordered_map <int, int> M;
  int solve(int a, int y, int p){
    M.clear();
    int B = sqrt(p);
    int w1 = y, u1 = power(a, p - 2, p);
    int w2 = 1, u2 = power(a, B, p);
    for(int i = 0;i < B;++ i){
      M[w1] = i;
      w1 = 1ll * w1 * u1 % p;
    }
    for(int i = 0;i < p / B;++ i){
      if(M.count(w2)){
        return i * B + M[w2];
      }
      w2 = 1ll * w2 * u2 % p;
    }
    return -1;
  } // a ^ x = y (mod p)
}

树图计数

LGV 定理叙述

设 \(G\) 是一张有向无环图,边带权,每个点的度数有限。给定起点集合 \(A=\{a_1,a_2, \cdots,a_n\}\),终点集合 \(B = \{b_1, b_2, \cdots,b_n\}\)。

  • 一段路径 \(p:v_0\to^{w_1} v_1\to^{w_2} v_2\to \cdots \to^{w_k} v_k\) 的权值:\(\omega (p) = \prod w_i\)。
  • 一对顶点 \((a, b)\) 的权值:\(e(a, b) = \sum_{p:a\to b}\omega (p)\)。

设矩阵 \(M\) 如下: $$ M = \begin{pmatrix} e(a_1, b_1) & e(a_1, b_2) & \cdots & e(a_1, b_n) \ e(a_2, b_1) & e(a_2, b_2) & \cdots & e(a_2, b_n) \ \vdots & \vdots & \ddots & \vdots \ e(a_n, b_1) & e(a_n, b_2) & \cdots & e(a_n, b_n) \ \end{pmatrix} $$ 从 \(A\) 到 \(B\) 得到一个不相交的路径组 \(p=(p_1, p_2, \cdots,p_n)\),其中从 \(a_i\) 到达 \(b_{\pi_i}\),\(\pi\) 是一个排列。定义 \(\sigma(\pi)\) 是 \(\pi\) 逆序对的数量。

给出 LGV 的叙述如下: $$ \det(M) = \sum_{p:A\to B} (-1)^{\sigma (\pi)} \prod_{i=1}^n \omega(p_i) $$

可以将边权视作边的重数,那么 \(e(a, b)\) 就可以视为从 \(a\) 到 \(b\) 的不同路径方案数。

矩阵树定理

对于无向图,

  • 定义度数矩阵 \(D_{i, j} = [i=j]\deg(i)\);
  • 定义邻接矩阵 \(E_{i, j} = E_{j, i}\) 是从 \(i\) 到 \(j\) 的边数个数;
  • 定义拉普拉斯矩阵 \(L = D - E\)。

对于无向图的矩阵树定理叙述如下: \(\(t(G) = \det(L_i) = \frac{1}{n}\lambda_1\lambda_2\cdots \lambda_{n-1}\)\)

其中 \(L_i\) 是将 \(L\) 删去第 \(i\) 行和第 \(i\) 列得到的子式。

对于有向图,类似于无向图定义入度矩阵、出度矩阵、邻接矩阵 \(D^{\mathrm{in}}, D^{\mathrm{out}}, E\),同时定义拉普拉斯矩阵 \(L^{\mathrm{in}} = D^{\mathrm{in}} - E,L^{\mathrm{out}} - E\)。 \(\(\begin{aligned} t^{\mathrm{leaf}}(G, k) &= \det(L^{\mathrm{in}}_k) \\ t^{\mathrm{root}}(G, k) &= \det(L^{\mathrm{out}}_k) \\ \end{aligned}\)\)

其中 \(t^{\mathrm{leaf}}(G, k)\) 表示以 \(k\) 为根的叶向树,\(t^{\mathrm{root}}(G, k)\) 表示以 \(k\) 为根的根向树。

BEST 定理

对于一个有向欧拉图 \(G\),记点 \(i\) 的出度为 \(\mathrm{out}_ i\),同时 \(G\) 的根向生成树个数为 \(T\)。\(T\) 可以任意选取根。则 \(G\) 的本质不同的欧拉回路个数为: \(\(T \prod_{i}(\mathrm{out}_i - 1)!\)\)

图连通方案数

\(n\) 个点的图,\(k\) 个连通块,第 \(i\) 个大小为 \(s_i\),添加 \(k-1\) 条边使得它们连通的方案数: $$ Ans=n{k-2}\cdot\prod_{i=1}{k}s_i $$

中国剩余定理

定理

对于线性方程: $$ \begin{cases} x \equiv a_1 \pmod {m_1} \ x \equiv a_2 \pmod {m_2} \ \cdots \ x \equiv a_n \pmod {m_n} \ \end{cases} $$ 如果 \(a_i\) 两两互质,可以得到 \(x\) 的解 \(x\equiv L\pmod M\),其中 \(M=\prod m_i\),而 \(L\) 由下式给出:\(\(L = \left(\sum a_i m_i\times (\left(M/m_i\right)^{-1}\bmod m_i)\right)\bmod M\)\)

#include "../header.cpp"
i64 A[MAXN], B[MAXN], M = 1;
i64 exgcd(i64 a, i64 b, i64 &x, i64 &y);
int main(){
  int n; cin >> n;
  for(int i = 1;i <= n;++ i){
    cin >> B[i] >> A[i], M *= B[i];
  }
  i64 L = 0;
  for(int i = 1;i <= n;++ i){
    i64 m = M / B[i], b, k;
    exgcd(m, B[i], b, k);
    L = (L + (__int128)A[i] * m * b) % M;
  }
  L = (L % M + M) % M;
  cout << L << endl;
  return 0;
}

狄利克雷前缀和

\[s(i) = \sum_{d\mid i} f_{d}\]
#include "../header.cpp"
unsigned A[MAXN];
int p, P[MAXN]; bool V[MAXN];
void solve(int n){
  for(int i = 2;i <= n;++ i){
    if(!V[i]){
      P[++ p] = i;
      for(int j = 1;j <= n / i;++ j){
        A[j * i] += A[j];
      } // 前缀和
    }
    for(int j = 1;j <= p&&P[j] <= n / i;++ j){
      V[i * P[j]] = true;
      if(i % P[j] == 0) break;
    }
  }
}

万能欧几里得

类欧几里得(万能欧几里得)

一种神奇递归,对 \(\displaystyle y=\left\lfloor \frac{Ax+B}{C}\right\rfloor\) 向右和向上走的每步进行压缩,做到 \(O(\log V)\) 复杂度。其中 \(A\ge C\) 就是直接压缩,向右之后必有至少 \(\lfloor A/C\rfloor\) 步向上。\(A<C\) 实际上切换 \(x,y\) 轴后,相当于压缩了一个上取整折线,而上取整下取整可以互化,便又可以递归。

代码中从 \((0,0)\) 走到 \((n,\lfloor (An+B)/C\rfloor)\),假设了 \(A,B,C\ge 0,C\neq 0\)(类欧基本都作此假设),\(U,R\) 矩阵是从右往左乘的,对列向量进行优化,和实际操作顺序恰好相反。快速幂的 log 据说可以被递归过程均摊掉,实际上并不会导致变成两个 log。

Matrix solve(ll n, ll A, ll B, ll C, Matrix R, Matrix U) {  // (0, 0) 走到 (n, (An+B)/C)
    if (A >= C) return solve(n, A % C, B, C, U.qpow(A / C) * R, U);
    ll l = B / C, r = (A * n + B) / C;
    if (l == r) return R.qpow(n) * U.qpow(l);   // l = r -> l = r or A = 0 or n = 0.
    ll p = (C * r - B - 1) / A + 1;
    return R.qpow(n - p) * U * solve(r - l - 1, C, C - B % C + A - 1, A, U, R) * U.qpow(l);
}

扩展欧几里得

内容

给定 \(a, b\),求出 \(ax+by=\gcd(a, b)\) 的一组 \(x, y\)。

int exgcd(int a, int b, int &x, int &y){
  if(a == 0){
    x = 0, y = 1; return b;
  } else {
    int x0 = 0, y0 = 0;
    int d = exgcd(b % a, a, x0, y0);
    x = y0 - (b / a) * x0, y = x0;
    return d;
  }
}

快速离散对数

用法

给定原根 \(g\) 以及模数 \(\mathrm{M}\),\(T\) 次询问 \(x\) 的离散对数。

复杂度 \(\mathcal O(\mathrm{M}^{2/3} + T \log \mathrm{M})\)。

#include "../header.cpp"
namespace BSGS {
  unordered_map <int, int> M;
  int B, U, P, g;
  void init(int g, int P0, int B0);
  int solve(int y);
}
const int MAXN = 1e5 + 3;
int H[MAXN], P[MAXN], H0, p, h, g, M;
bool V[MAXN];
int solve(int x){
  if(x <= h) return H[x];
  int v = M / x, r = M % x;
  if(r < x - r) return ((H0 + solve(r)) % (M - 1) - H[v] + M - 1) % (M - 1);
  else          return (solve(x - r) - H[v + 1] + M - 1) % (M - 1);
}
int main(){
  ios :: sync_with_stdio(false);
  cin.tie(nullptr);
  cin >> g >> M;
  h = sqrt(M) + 1;
  BSGS :: init(g, M, sqrt(1ll * M * sqrt(M) / log10(M)));
  H0 = BSGS :: solve(M - 1);
  H[1] = 0;
  for(int i = 2;i <= h;++ i){
    if(!V[i]){
      P[++ p] = i;
      H[i] = BSGS :: solve(i);
    }
    for(int j = 1;j <= p&&P[j] <= h / i;++ j){
      int &p = P[j];
      H[i * p] = (H[i] + H[p]) % (M - 1);
      V[i * p] = true;
      if(i % p == 0) break;
    }
  }
  int T; cin >> T;
  while(T --){
    int x; cin >> x;
    cout << solve(x) << "\n";
  }
  return 0;
}

原根

#include "../header.cpp"
int getphi(int x);              // 求解 phi
vector <int> getprime(int x);   // 求解质因数
bool test(int g, int m, int mm,vector<int>&P){
  for(auto &p: P)
    if(power(g, mm / p, m) == 1)
      return false;
  return true;
}
int get_genshin(int m){
  int mm = getphi(m);
  vector <int> P = getprime(mm);
  for(int i = 1;;++ i)
    if(test(i, m, mm, P)) return i;
}

拉格朗日插值

定理

给定 \(n\) 个横坐标不同的点 \((x_i, y_i)\),可以唯一确定一个 \(n - 1\) 阶多项式如下: $$ f(x) = \sum_{i=1}^n \frac{\prod_{j\neq i} (x-x_j)}{\prod_{j\neq i}(x_i-x_j)} \cdot y_i $$

min-max 容斥

定理

$$ \begin{aligned} \max_{i\in S} {x_i} &= \sum_{T\subseteq S}(-1)^{|T| - 1}\min_{j\in T}{x_j} \ \min_{i\in S} {x_i} &= \sum_{T\subseteq S}(-1)^{|T| - 1}\max_{j\in T}{x_j} \ \end{aligned} $$ 期望意义下上式依然成立。

另外设 \(\max^k\) 表示第 \(k\) 大的元素,可以推广为如下式子: $$ \max_{i\in S}^k {x_i} = \sum_{T\subseteq S}(-1)^{|T| - k}\binom{|T - 1|}{k - 1} \min_{j\in T}{x_j} $$ 此外在数论上可以得到: $$ \operatorname*{lcm}{i\in S} {x_i} = \prod{T\subseteq S} \left(\gcd_{j\in T}{x_j}\right){(-1){|T| - 1}} $$

应用

对于计算“\(n\) 个属性都出现的期望时间”问题,设第 \(i\) 个属性第一次出现的时间是 \(t_i\),所求即为 \(\max(t_i)\),使用 min-max 容斥转为计算 \(\min(t_i)\)。

比如 \(n\) 个独立物品,每次抽中物品 \(i\) 的概率是 \(p_i\),问期望抽多少次抽中所有物品。那么就可以计算 \(\min_S\) 表示第一次抽中物品集合 \(S\) 内物品的时间,可以得到: $$ \max_{U}=\sum_{S\subseteq U}(-1)^{|S| - 1}\min_S = \sum_{S\subseteq U}(-1)^{|S| - 1}\cdot \frac{1}{\sum _{x\in S}p_x} $$

Barrett 取模

用法

调用 init 计算出 \(S\) 和 \(X\),得到计算 \(\lfloor x / P \rfloor = (x\times X) / 2^{60 + S}\)。从而计算 \(x \bmod P = x - P \times \lfloor x / P \rfloor\)。

#include "../header.cpp"
i64 S = 0, X = 0;
void init(int MOD){
  while((1 << (S + 1)) < MOD) S ++;
  X = ((i80)1 << 60 + S) / MOD + !!(((i80)1 << 60 + S) % MOD);
  cerr << S << " " << X << endl;
}
int power(i64 x, int y, int MOD){
  i64 r = 1;
  while(y){
    if(y & 1){
      r = r * x;
      r = r - MOD * ((i80)r * X >> 60 + S);
    }
    x = x * x;
    x = x - MOD * ((i80)x * X >> 60 + S);
    y >>= 1;
  }
  return r;
}

Pollard's Rho

用法

  • 调用 test(n) 判断 \(n\) 是否是质数;
  • 调用 rho(n) 计算 \(n\) 分解质因数后的结果,不保证结果有序。
#include "../header.cpp"
i64 step(i64 a, i64 c, i64 m){
  return ((i80)a * a + c) % m;
}
i64 multi(i64 a, i64 b, i64 m){
  return (i80) a * b % m;
}
i64 power(i64 a, i64 b, i64 m){
  i64 r = 1;
  while(b){
    if(b & 1) r = multi(r, a, m);
    b >>= 1,  a = multi(a, a, m);
  }
  return r;
}
mt19937_64 MT;
bool test(i64 n){
  if(n < 3 || n % 2 == 0) return n == 2;
  i64 u = n - 1, t = 0;
  while(u % 2 == 0) u /= 2, t += 1;
  int test_time = 20;
  for(int i = 1; i <= test_time;++ i){
    i64 a = MT() % (n - 2) + 2;
    i64 v = power(a, u, n);
    if(v == 1) continue;
    int s;
    for(s = 0;s < t;++ s){
      if(v == n - 1) break;
      v = multi(v, v, n);
    }
    if(s == t) return false;
  }
  return true;
}
basic_string<i64> rho(i64 n){
  if(n == 1)  return { };
  if(test(n)) return {n};
  i64 a  = MT() % (n - 1) + 1;
  i64 x1 = MT() % (n - 1), x2 = x1;
  for(int i = 1;;i <<= 1){
    i64 tot = 1;
    for(int j = 1;j <= i;++ j){
      x2 = step(x2, a, n);
      tot = multi(tot, llabs(x1 - x2), n);
      if(j % 127 == 0){
        i64 d = __gcd(tot, n);
        if(d > 1)
          return rho(d) + rho(n / d);
      }
    }
    i64 d = __gcd(tot, n);
    if(d > 1)
      return rho(d) + rho(n / d);
    x1 = x2;
  }
}

polya 定理

Burnside 引理

记所有染色方案的集合为 \(X\),其中单个染色方案为 \(x\)。一种对称操作 \(g\in X\) 作用于染色方案 \(x\in X\) 上可以得到另外一种染色 \(x'\)。

将所有对称操作作为集合 \(G\),那么 \(Gx = \{gx \mid g\in G\}\) 是与 \(x\) 本质相同的染色方案的集合,形式化地称为 \(x\) 的轨道。统计本质不同染色方案数,就是统计不同轨道个数。

Burnside 引理说明如下: $$ |X / G| = \frac{1}{|G|} \sum_{g\in G}|X^g| $$ 其中 \(X^g\) 表示在 \(g\in G\) 的作用下,不动点的集合。不动点被定义为 \(x = gx\) 的 \(x\)。

Polya 定理

对于通常的染色问题,\(X\) 可以看作一个长度为 \(n\) 的序列,每个元素是 \(1\) 到 \(m\) 的整数。可以将 \(n\) 看作面数、\(m\) 看作颜色数。Polya 定理叙述如下: $$ |X / G| = \frac{1}{|G|} \sum_{g\in G}\sum_{g\in G} m^{c(g)} $$ 其中 \(c(g)\) 表示对一个序列做轮换操作 \(g\) 可以分解成多少个置换环。

然而,增加了限制(比如要求某种颜色必须要多少个),就无法直接应用 Polya 定理,需要利用 Burnside 引理进行具体问题具体分析。

应用

给定 \(n\) 个点 \(n\) 条边的环,现在有 \(n\) 种颜色,给每个顶点染色,询问有多少种本质不同的染色方案。

显然 \(X\) 是全体元素在 \(1\) 到 \(n\) 之间长度为 \(n\) 的序列,\(G\) 是所有可能的单次旋转方案,共有 \(n\) 种,第 \(i\) 种方案会把 \(1\) 置换到 \(i\)。于是: $$ \begin{aligned} \mathrm{ans} &= \frac{1}{|G|} \sum_{i=1}^n m^{c(g_i)} \ &= \frac{1}{n} \sum_{i=1}^{n} n^{\gcd(i,n)} \ &= \frac{1}{n} \sum_{d\mid n}^n n^{d} \sum_{i=1}^n [\gcd(i,n) = d] \ &= \frac{1}{n} \sum_{d\mid n}^n n^{d} \varphi(n/d) \ \end{aligned} $$

#include "../header.cpp"
vector <tuple<int, int> > P;
void solve(int step, int n, int d, int f, int &ans){
  if(step == P.size()){
    ans = (ans + 1ll * power(n, n / d) * f) % MOD;
  } else {
    auto [w, c] = P[step];
    int dd = 1, ff = 1;
    for(int i = 0;i <= c;++ i){
      solve(step + 1, n, d * dd, f * ff, ans);
      ff = ff * (w - (i == 0)), dd = dd * w;
    }
  }
}
int main(){
  int T; cin >> T;
  while(T --){
    int n, t; cin >> n; t = n;
    for(int i = 2;i <= n / i;++ i) if(n % i == 0){
      int w = i, c = 0;
      while(t % i == 0) t /= i, c ++;
      P.push_back({ w, c });
    }
    if(t != 1) P.push_back({ t, 1 });
    int ans = 0;
    solve(0, n, 1, 1, ans);
    ans = 1ll * ans * power(n, MOD - 2) % MOD;
    cout << ans << endl;
    P.clear();
  }
  return 0;
}

min25 筛

设有一个积性函数 \(f(n)\),满足 \(f(p^k)\) 可以快速求,考虑搞一个在质数位置和 \(f(n)\) 相等的 \(g(n)\),满足它有完全积性,并且单点和前缀和都可以快速求,然后通过第一部分筛出 \(g\) 在质数位置的前缀和,从而相当于得到 \(f\) 在质数位置的前缀和,然后利用它,做第二部分,求出 \(f\) 的前缀和。

  1. \(G_k(n)=\sum_{i=1}^{n}[\text{mindiv}(i)>p_k{~\text{or}~}\text{isprime}(i)]g(i)\)(\(p_0=1\)),则有 \(G_k(n)=G_{k-1}(n)-g(p_k) \times (G_{k-1}(n/p_k)-G_{k-1}(p_{k-1}))\),复杂度 \(O({n^{3/4}}/{\log n})\)。
  2. \(F_k(n)=\sum_{i=1}^{n}[\text{mindiv}(i)\ge p_k]f(i) = \sum[h\ge k, p_h^2\le n]\sum[c \ge 1, p_h^{c+1}\le n](f(p_h^c)F_{h+1}(n/p_h^c)+f(p_h^{c+1}))+F_{\text{prime}}(n)-F_{\text{prime}}(p_{k-1})\),在 \(n\le 10^{13}\) 可以证明复杂度 \(O(n^{3/4}/\log n)\)。

常见细节问题:

  • 由于 \(n\) 通常是 \(10^{10}\) 到 \(10^{11}\) 的数,导致 \(n\) 会爆 int,\(n^2\) 会爆 long long,而且往往会用自然数幂和,更容易爆,所以要小心。
  • 记 \(s=\lfloor \sqrt{n}\rfloor\),由于 \(F\) 递归时会去找 \(F_{h+1}\),会访问到 \(s\) 以内最大的质数往后的一个质数,而已经证明对于所有 \(n\in\mathbb{N}^+\),\([n+1,2n]\) 中有至少一个质数,所以只需要筛到 \(2s\) 即可。
  • 注意补回 \(f(1)\)。
// 预处理,$1$ 所在的块也算进去了
namespace init {
    ll init_n, sqrt_n;
    vector<ll> np, p, id1, id2, val;
    ll cnt;
    void main(ll n) {
        init_n = n, sqrt_n = sqrt(n);
        ll M = sqrt_n * 2; // 筛出一个 > floor(sqrt(n)) 的质数, 避免后续讨论边界
        np.resize(M + 1), p.resize(M + 1);
        for (ll i = 2; i <= M; ++i) {
            if (!np[i]) p[++p[0]] = i;
            for (ll j = 1; j <= p[0]; ++j) {
                if (i * p[j] > M) break;
                np[i * p[j]] = 1;
                if (i % p[j] == 0) break;
            }
        }
        p[0] = 1;
        id1.resize(sqrt_n + 1), id2.resize(sqrt_n + 1);
        val.resize(1);
        for (ll l = 1, r, v; l <= n; l = r + 1) {
            v = n / l, r = n / v;
            if (v <= sqrt_n) id1[v] = ++cnt;
            else id2[init_n / v] = ++cnt;
            val.emplace_back(v);
        }
    }
    ll id(ll n) {
        if (n <= sqrt_n) return id1[n];
        else return id2[init_n / n];
    }
}
using namespace init;
// 计算 $G_k$,两个参数分别是 $g$ 从 $2$ 开始的前缀和和 $g$
auto calcG = [&] (auto&& sum, auto&& g) -> vector<ll> {
    vector<ll> G(cnt + 1);
    for (int i = 1; i <= cnt; ++i) G[i] = sum(val[i]);
    ll pre = 0;
    for (int i = 1; p[i] * p[i] <= n; ++i) {
        for (int j = 1; j <= cnt; ++j) {
            if (p[i] * p[i] > val[j]) break;
            ll tmp = id(val[j] / p[i]);
            G[j] = (G[j] - g(p[i]) * (G[tmp] - pre)) % MD;
        }
        pre = (pre + g(p[i])) % MD;
    }
    for (int i = 1; i <= cnt; ++i) G[i] = (G[i] % MD + MD) % MD;
    return G;
};
// 计算 $F_k$,直接搜,不用记忆化。`fp` 是 $F_{\text{prime}}$,`pc` 是 $p^c$,其中 `f(p[h] ^ c)` 要替换掉。
function<ll(ll, int)> calcF = [&] (ll m, int k) {
    if (p[k] > m) return 0;
    ll ans = (fp[id(m)] - fp[id(p[k - 1])]) % MD;
    for (int h = k; p[h] * p[h] <= m; ++h) {
        ll pc = p[h], c = 1;
        while (pc * p[h] <= m) {
            ans = (ans + calcF(m / pc, h + 1) * f(p[h] ^ c)) % MD;
            ++c, pc = pc * p[h], ans = (ans + f(p[h] ^ c)) % MD;
        }
    }
    return ans;
};

杜教筛

用法

对于积性函数 \(f\),找到易求前缀和的积性函数 \(g, h\) 使得 \(h = f*g\),根据递推式计算 \(S(n) = \sum_{i=1}^n f(i)\):$$ S(n) = H(n) - \sum_{d = 1}^n g(d) \times S(\left\lfloor \frac{n}{d}\right\rfloor) $$

例题

  • 对于 \(f = \varphi\),寻找 \(g = 1, h = \mathrm{id}\);
  • 对于 \(f = \mu\),寻找 \(g = 1, h = \varepsilon\)。

二次剩余

用法

多次询问,每次询问给定奇素数 \(p\) 以及 \(y\),在 \(\mathcal O(\log p)\) 复杂度计算 \(x\) 使得 \(x^2 \equiv 0 \pmod p\) 或者无解。

#include "../header.cpp"
// 检查 x 在模 p 意义下是否有二次剩余
bool check(int x, int p){
  return power(x, (p - 1) / 2, p) == 1;
}
struct Node { int real, imag; };
Node mul(const Node a, const Node b, int p, int v){
  int nreal = (1ll * a.real * b.real
    + 1ll * a.imag * b.imag % p * v) % p;
  int nimag = (1ll * a.real * b.imag
    + 1ll * a.imag * b.real) % p;
  return { (nreal), nimag };
}
Node power(Node a, int b, int p, int v){
  Node r = { 1, 0 };
  while(b){
    if(b & 1) r = mul(r, a, p, v);
    b >>= 1,  a = mul(a, a, p, v);
  }
  return r;
}
mt19937 MT;
// 无解 x1 = x2 = -1,唯一解 x1 = x2
void solve(int n, int p, int &x1, int &x2){
  if(n == 0){ x1 = x2 =  0; return; }
  if(!check(n, p)){ x1 = x2 = -1; return; }
  i64 a, t;
  do {
    a = MT() % p;
  }while(check(t = (a * a - n + p) % p, p));
  Node u = { a, 1 };
  x1 = power(u, (p + 1) / 2, p, t).real;
  x2 = (p - x1) % p;
  if(x1 > x2) swap(x1, x2);
}

单位根反演

定理

给出单位根反演如下: $$ [d\mid n] = \frac{1}{d}\sum_{i=0}{d-1}\omega_{d}{ni} $$

多项式

最小多项式

#include "../header.cpp"
vector<int> bm(const vector<int> &a) {
  vector<int> v, ls;
  int k = -1, delta = 0;
  for (int i = 0; i < a.size();i ++) {
    int tmp = 0;
    for (int j = 0;j < v.size();j ++)
      tmp = (tmp + (ll)a[i - j - 1] * v[j]) % MD;
    if (a[i] == tmp) continue;
    if (k < 0) { k = i; delta = (a[i] - tmp + MD) % MD; v = vector<int>(i + 1); continue; }
    vector<int> u = v;
    int val = (ll)(a[i] - tmp + MD) * qpow(delta, MD - 2) % MD;
    v.resize(max(v.size(), ls.size()+ i - k));
    (v[i - k - 1] += val) %= MD;
    for (int j = 0; j < (int)ls.size(); j++){
      v[i - k + j] = (v[i - k + j] - (ll)val * ls[j]) % MD;
      if(v[i - k + j] < 0) v[i - k + j] += MD;
    }
    if (u.size() + k < ls.size() + i){
      ls = u; k = i, delta = a[i] - tmp;
      if (delta < 0) delta += MD;
    }
  }
  for (auto &x : v) x = (MD - x) % MD;
  v.insert(v.begin(), 1);
  return v;
} // $\forall i, \sum_{j = 0} ^ m a_{i - j} v_j = 0$

NTT 全家桶

#include "../header.cpp"
using Poly = vector<ll>;
#define lg(x) ((x) == 0 ? -1 : __lg(x))
#define Size(x) int(x.size())
namespace NTT_ns {
  const long long G = 3, invG = inv(G);
  vector<int> rev;
  void NTT(ll* F, int len, int sgn) {
    rev.resize(len);
    for (int i = 1; i < len; ++i) {
      rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (len >> 1));
      if (i < rev[i]) swap(F[i], F[rev[i]]);
    }
    for (int tmp = 1; tmp < len; tmp <<= 1) {
      ll w1 = qpow(sgn ? G : invG,
        (MD - 1) / (tmp << 1));
      for (int i = 0; i < len; i += tmp << 1){
        for(ll j = 0, w = 1; j < tmp; ++j,
            w = w * w1 % MD) {
          ll x = F[i + j];
          ll y = F[i + j + tmp] * w % MD;
          F[i + j] = (x + y) % MD;
          F[i + j + tmp] = (x - y + MD) % MD;
        }
      }
    }
    if (sgn == 0) {
      ll inv_len = inv(len);
      for (int i = 0; i < len; ++i)
        F[i] = F[i] * inv_len % MD;
    }
  }
}
using NTT_ns::NTT;
Poly operator * (Poly F, Poly G) {
  int siz = Size(F) + Size(G) - 1;
  int len = 1 << (lg(siz - 1) + 1);
  if (siz <= 300) {
    Poly H(siz);
    for (int i = Size(F) - 1; ~i; --i)
      for (int j = Size(G) - 1; ~j; --j)
        H[i + j] =(H[i + j] + F[i] * G[j])%MD;
    return H;
  }
  F.resize(len), G.resize(len);
  NTT(F.data(), len, 1),NTT(G.data(), len, 1);
  for (int i = 0; i < len; ++i)
    F[i] = F[i] * G[i] % MD;
  NTT(F.data(), len, 0), F.resize(siz);
  return F;
}
Poly operator + (Poly F, Poly G) {
  int siz = max(Size(F), Size(G));
  F.resize(siz), G.resize(siz);
  for (int i = 0; i < siz; ++i)
    F[i] = (F[i] + G[i]) % MD;
  return F;
}
Poly operator - (Poly F, Poly G) {
  int siz = max(Size(F), Size(G));
  F.resize(siz), G.resize(siz);
  for (int i = 0; i < siz; ++i)
    F[i] = (F[i] - G[i] + MD) % MD;
  return F;
}
Poly lsh(Poly F, int k) {
  F.resize(Size(F) + k);
  for (int i = Size(F) - 1; i >= k; --i)
    F[i] = F[i - k];
  for (int i = 0; i < k; ++i) F[i] = 0;
  return F;
}
Poly rsh(Poly F, int k) {
  int siz = Size(F) - k;
  for (int i = 0; i < siz;++i)F[i] = F[i + k];
  return F.resize(siz), F;
}
Poly cut(Poly F, int len) {
  return F.resize(len), F;
}
Poly der(Poly F) {
  int siz = Size(F) - 1;
  for (int i = 0; i < siz; ++i)
    F[i] = F[i + 1] * (i + 1) % MD;
  return F.pop_back(), F;
}
Poly inte(Poly F) {
  F.emplace_back(0);
  for (int i = Size(F) - 1; ~i; --i)
    F[i] = F[i - 1] * inv(i) % MD;
  return F[0] = 0, F;
}
Poly inv(Poly F) {
  int siz = Size(F); Poly G{inv(F[0])};
  for (int i = 2; (i >> 1) < siz; i <<= 1) {
    G= G + G - G * G * cut(F, i), G.resize(i);
  }
  return G.resize(siz), G;
}
Poly ln(Poly F) {
  return cut(inte(cut(der(F) * inv(F), Size(F))), Size(F));
}
Poly exp(Poly F) {
  int siz = Size(F); Poly G{1};
  for (int i = 2; (i >> 1) < siz; i <<= 1) {
    G = G * (Poly{1} - ln(cut(G, i)) + cut(F, i)), G.resize(i);
  }
  return G.resize(siz), G;
}

定系数短多项式卷积

有 \(n\) 个多项式 \(\displaystyle F_i=\sum_{j=1}^{k}w_jx^{a_{i,j}}\),\((w_i)\) 序列固定,并且 \(k\) 很小,\(a_i\in[0,2^m)\),现在要把他们位运算卷积起来,求最终的序列。

异或版本,复杂度 \(O(2^k((n+m)k+2^m(m+\log V)))\),按通常大小关系可以认为是 \(O(nk2^k+m2^{m+k})\),最后那个 \(\log V\) 是快速幂,可以 \(O(n2^k)\) 预处理去掉:

#include "poly-fwt.cpp"
#define vv vector
int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  ll n, k, m;
  cin >> n >> m, k = 3;
  vv<ll> w(k);
  for(auto &i : w) cin >> i;
  vv<vv<ll>> a(n, vv<ll>(k));
  for(auto &i : a) for(auto &j : i) cin >> j;
  ll uk = 1 << k, V = 1 << m;
  vv<vv<ll>> c(V, vv<ll>(uk));
  vv<ll> val(uk);
  for (ll i = 0; i < uk; ++i) {
    for (ll p = 0; p < k; ++p)
      if ((i >> p) & 1)
        val[i] = (val[i] - w[p] + MD) % MD;
      else val[i] = (val[i] + w[p]) % MD;
    vv<ll> f(V);
    for (ll j = 0; j < n; ++j) {
      ll z = 0;
      for (ll p = 0; p < k; ++p)
        if ((i >> p) & 1) z ^= a[j][p];
      ++f[z];
    }
    FWT(f.data(), V, 1, 1, 1, MD - 1);
    for (ll j = 0; j < V; ++j) c[j][i] = f[j];
  }
  for (ll i = 0; i < V; ++i) FWT(c[i].data(), uk, inv2, inv2, inv2, MD - inv2);
  vv<ll> Ans(V, 1);
  for (ll i = 0; i < V; ++i)
    for (ll j = 0; j < uk; ++j)
      Ans[i] = Ans[i] * qpow(val[j], c[i][j]) % MD;
  FWT(Ans.data(), V, inv2, inv2, inv2, MD - inv2);
  for (ll i = 0; i < V; ++i)
    cout << Ans[i] << " \n"[i == V - 1];
  return 0;
}

FWT 全家桶

\[ (FA)_i = \sum_j \prod_{k = n}^0 c(B(i, k), B(j, k)) A_j, c_{*, i} c_{*, j} = c_{*, i\oplus j} \]
#include "../header.cpp"
void FWT(ll *F, ll len, ll c00, ll c01, ll c10, ll c11) {
  for (ll t = 1; t < len; t <<= 1) {
    for (ll i = 0; i < len; i += t * 2) {
      for (ll j = 0; j < t; ++j) {
        ll x = F[i + j];
        ll y = F[i + j + t];
      F[i + j] = (x * c00 + y * c01) % MD;
      F[i + j + t] = (x * c10 + y * c11) % MD;
      }
    }
  }
}

任意模数 NTT

#include "../header.cpp"
using cp = complex<ld>;
vector<int> rev;
vector<cp> om;   // exp(sgn * pi * k / len)
void FFT(cp* F, int len, int sgn) {
  rev.resize(len);
  for (int i = 1; i < len; ++i) {
    rev[i] = (rev[i >> 1] >> 1)
      | ((i & 1) * (len >> 1));
    if (i < rev[i]) swap(F[i], F[rev[i]]);
  }
  const ld pi = std::acos(ld(-1));
  om.resize(len);
  for (int i = 0; i < len; ++i) {
    om[i] = polar(ld(1), sgn * i * pi / len);
  }
  for (int tmp = 1; tmp < len; tmp <<= 1){
    for (int i = 0; i < len; i += tmp << 1){
      int K = len / tmp, pos = 0;
      for (int j = 0; j < tmp; ++j, pos += K){
        cp x = F[i + j];
        cp y = F[i + j + tmp] * om[pos];
        F[i + j] = x + y;
        F[i + j + tmp] = x - y;
      }
    }
  }
  if (sgn == -1) {
    cp inv_len(ld(1) / len);
    for (int i = 0; i < len; ++i)
      F[i] = F[i] * inv_len;
  }
}
ll MD, M; // 输入 MD 后, 需要设置 M 为 sqrt(MD)
using Poly = vector<ll>;
Poly polyMul(Poly F, Poly G, int tmp = 0) { // tmp 用于循环卷积技巧, 卡常
  for (auto &k : F) k %= MD;
  for (auto &k : G) k %= MD;
  int n = (int)F.size() - 1, m = (int)G.size() - 1;
  if (tmp == 0) tmp = n + m + 1;
  int len = 1;
  while (len < tmp) len <<= 1;
  vector<cp> P(len), tP(len), Q(len);
  for (int i = 0; i <= n; ++i)
    P[i] = cp(F[i] / M, F[i] % M),
    tP[i] = cp(F[i] / M, -(F[i] % M));
  for (int i = 0; i <= m; ++i)
    Q[i] = cp(G[i] / M, G[i] % M);
  for(auto &X: {&P, &Q, &tP})
    FFT(X -> data(), len, 1);
  for (int i = 0; i < len; ++i)
    P[i] *= Q[i], tP[i] *= Q[i];
  FFT(P.data(), len, -1);
  FFT(tP.data(), len, -1);
  vector<ll> H(n + m + 1);
  for (int i = 0; i < tmp; ++i) {
    H[i] = ll((P[i].real() + tP[i].real()) / 2 + 0.5) % MD * M % MD * M % MD
       + ll(P[i].imag() + 0.5) % MD * M % MD
       + ll((tP[i].real() - P[i].real()) / 2 + 0.5) % MD;
    H[i] = (H[i] + MD) % MD;
  }
  return H;
}

字符串

AC 自动机

#include "../header.cpp"
namespace ACAM{
  int C[MAXN][MAXM], F[MAXN], o;
  void insert(char *S){
    int p = 0, len = 0;
    for(int i = 0;S[i];++ i){
      int e = S[i] - 'a';
      if(C[p][e]) p = C[p][e];
        else      p = C[p][e] = ++ o;
      ++ len;
    }
  }
  void build(){
    queue <int> Q; Q.push(0);
    while(!Q.empty()){
      int u = Q.front(); Q.pop();
      for(int i = 0;i < 26;++ i){
        int p = F[u], v = C[u][i];
        if(v == 0) continue;
        while(!C[p][i] && p != 0) p = F[p];
        if(C[p][i] && C[p][i] != v)
          F[v] = C[p][i];
        Q.push(v);
      }
    }
  }
}

扩展 KMP

定义

\[ \begin{aligned} z_i &= |\mathrm{lcp}(b, \mathrm{suffix}(b, i))| \\ \end{aligned} \]
#include "../header.cpp"
int Z[MAXN];
void exkmp(char A[]){
  int l = 0, r = 0; Z[1] = 0;
  for(int i = 2;A[i];++ i){
    Z[i] = i <= r ? min(r - i + 1, Z[i - l + 1]) : 0;
    while(A[Z[i] + 1] == A[i + Z[i]]) ++ Z[i];
    if(i + Z[i] - 1 > r)
      r = i + Z[i] - 1, l = i;
  }
}

最小表示法

#include "../header.cpp"
int min_pos(const vector<int> &a) {
  int n = a.size(), i = 0, j = 1, k = 0;
  while (i < n && j < n && k < n) {
    int u = a[(i + k) % n],v = a[(j + k) % n];
    int t = u > v ? 1 : (u < v ? -1 : 0);
    if (t == 0) k ++; else {
      if (t > 0) i += k + 1; else j += k + 1;
      if (i == j) j++;
      k = 0;
    }
  }
  return min(i, j);
}

回文自动机

#include "../header.cpp"
namespace PAM{
  const int SIZ = 5e5 + 3;
  int n, s, F[SIZ], L[SIZ], D[SIZ];
  int M[SIZ][MAXM];
  char S[SIZ];
  void init(){
    S[0] = '#', n = 1;
    F[s = 0] = -1, L[0] = -1, D[0] = 0;
    F[s = 1] =  0, L[1] =  0, D[1] = 0;
  }
  void extend(int &last, char c){
    S[++ n] = c;
    int e = c - 'a', a = last;
    while(c != S[n - 1 - L[a]]) a = F[a];
    if(M[a][e]){
      last = M[a][e];
    } else {
      int cur = M[a][e] = ++ s;
      L[cur] = L[a] + 2;
      if(a == 0){
        F[cur] = 1;
      } else {
        int b = F[a];
        while(c != S[n - 1 - L[b]])
          b = F[b];
        F[cur] = M[b][e];
      }
      D[cur] = D[F[cur]] + 1, last = cur;
    }
  }
}

后缀数组(倍增)

#include "../header.cpp"
int n, m, A[MAXN], B[MAXN], C[MAXN], R[MAXN], P[MAXN], Q[MAXN];
char S[MAXN];
int main(){
  scanf("%s", S), n = strlen(S), m = 256;
  for(int i = 0;i < n;++ i) R[i] = S[i];
  for (int k = 1;k <= n;k <<= 1){
    for(int i = 0;i < n;++ i){
      Q[i] = ((i + k > n - 1) ? 0 : R[i + k]);
      P[i] = R[i];
      m = max(m, R[i]);
    }
#define fun(a, b, c) \
memset(C, 0, sizeof(int) * (m + 1));         \
for(int i = 0;i <  n;++ i) C[a] += 1;        \
for(int i = 1;i <= m;++ i) C[i] += C[i - 1]; \
for(int i = n - 1;i >= 0;-- i) c[-- C[a]] = b;
    fun(Q[  i ],   i , B)
    fun(P[B[i]], B[i], A)
#undef fun
    int p = 1; R[A[0]] = 1;
    for(int i = 1;i <= n - 1;++ i){
      bool f1 = P[A[i]] == P[A[i - 1]];
      bool f2 = Q[A[i]] == Q[A[i - 1]];
      R[A[i]] = f1 && f2 ? R[A[i - 1]] : ++ p;
    }
    if (m == n) break;
  }
  for(int i = 0;i < n;++ i)
    printf("%u ", A[i] + 1);
  return 0;
}

广义后缀自动机(离线)

#include "../header.cpp"
namespace SAM{
  const int SIZ = 2e6 + 3;
  int M[SIZ][MAXM], L[SIZ], F[SIZ], S[SIZ], s = 0, h = 25;
  void init(){ F[0] = -1, s = 0; }
  void extend(int &last, char c){
    int e = c - 'a';
    int cur = ++ s;
    L[cur] = L[last] + 1;
    int p = last;
    while(p != -1 && !M[p][e])
      M[p][e] = cur, p = F[p];
    if(p == -1){
      F[cur] = 0;
    } else {
      int q = M[p][e];
      if(L[p] + 1 == L[q]){
        F[cur] = q;
      } else {
        int clone = ++ s;
        L[clone] = L[p] + 1, F[clone] = F[q];
        for(int i = 0;i <= h;++ i)
          M[clone][i] = M[q][i];
        while(p != -1 && M[p][e] == q)
          M[p][e] = clone, p = F[p];
        F[cur] = F[q] = clone;
      }
    }
    last = cur;
  }
}
namespace Trie{
  int M[MAXN][MAXM], O[MAXN], s, h = 25;
  void insert(char *S);
  void build_sam(){
    queue <int> Q; Q.push(0);
    while(!Q.empty()){
      int u = Q.front(); Q.pop();
      for(int i = 0;i <= h;++ i){
        char c = i + 'a';
        if(M[u][i]){
          int v = M[u][i];
          O[v] = O[u];
          SAM :: extend(O[v], c);
          Q.push(v);
        }
      }
    }
  }
}

广义后缀自动机(在线)

#include "../header.cpp"
namespace SAM{
  int M[MAXN][MAXM], L[MAXN], F[MAXN], S[MAXN], s = 0, h = 25;
  void init(){
    F[0] = -1, s = 0;
  }
  // 每次插入新字符串前将 last 清零
  void extend(int &last, char c){
    int e = c - 'a';
    if(M[last][e]){
      int p = last;
      int q = M[last][e];
      if(L[q] == L[last] + 1){
        last = q;
      } else {
        int clone = ++ s;
        L[clone] = L[p] + 1, F[clone] = F[q];
        for(int i = 0;i <= h;++ i)
          M[clone][i] = M[q][i];
        while(p != -1 && M[p][e] == q)
          M[p][e] = clone, p = F[p];
        F[q] = clone;
        last = clone;
      }
    } else {
      int cur = ++ s;
      L[cur] = L[last] + 1;
      int p = last;
      while(p != -1 && !M[p][e])
        M[p][e] = cur, p = F[p];
      if(p == -1){
        F[cur] = 0;
      } else {
        int q = M[p][e];
        if(L[p] + 1 == L[q]){
          F[cur] = q;
        } else {
          int clone = ++ s;
          L[clone] = L[p] + 1,F[clone] = F[q];
          for(int i = 0;i <= h;++ i)
            M[clone][i] = M[q][i];
          while(p != -1 && M[p][e] == q)
            M[p][e] = clone, p = F[p];
          F[cur] = F[q] = clone;
        }
      }
      last = cur;
    }
  }
}

后缀自动机

#include "../header.cpp"
namespace SAM{
  int M[MAXN][MAXM], L[MAXN], F[MAXN], S[MAXN], last = 0, s = 0, h = 25;
  void init(){
    F[0] = -1, last = s = 0;
  }
  void extend(char c){
    int cur = ++ s, e = c - 'a';
    L[cur] = L[last] + 1, S[cur] = 1;
    int p = last;
    while(p != -1 && !M[p][e])
      M[p][e] = cur, p = F[p];
    if(p == -1){
      F[cur] = 0;
    } else {
      int q = M[p][e];
      if(L[p] + 1 == L[q]){
        F[cur] = q;
      } else {
        int clone = ++ s;
        L[clone] = L[p] + 1, F[clone] = F[q];
        S[clone] = 0;
        for(int i = 0;i <= h;++ i)
          M[clone][i] = M[q][i];
        while(p != -1 && M[p][e] == q)
          M[p][e] = clone, p = F[p];
        F[cur] = F[q] = clone;
      }
    }
    last = cur;
  }
}

计算几何

二维圆形

#include "2d.cpp"
struct circ: pp { db r; };  // 圆形
circ circ_i(pp a, pp b, pp c) {
  db A = dis(a,b), B = dis(a,c), C = dis(a,b);
  return {
    (a * A + b * B + c * C) / (A + B + C),
    abs((b - a) * (c - a)) / (A + B + C)
  };
}   // 三点确定内心
circ circ_2pp(pp a, pp b){
  return {(a + b) / 2, dis(a, b) / 2};
}   // 两点确定直径
circ circ_3pp(pp a, pp b, pp c) {
  pp bc = c - b, ca = a - c, ab = b - a;
  pp o = (b + c - r90(bc) * (ca % ab) / (ca * ab)) / 2;
  return {o, (a - o).abs()};
}   // 三点确定外心
circ minimal(vector <pp> V){  // 最小圆覆盖
  shuffle(V.begin(), V.end(), MT);
  circ C(V[0], 0);
  for(int i = 0;i < V.size();++ i) {
    if (dis((pp)C, V[i]) < C.r) continue;
    C = circ_2pp(V[i], V[0]);
    for(int j = 0;j < i;++ j) {
      if (dis((pp)C, V[j]) < C.r) continue;
      C = circ_2pp(V[i], V[j]);
      for(int k = 0;k < j;++ k) {
        if (dis((pp)C, V[k]) < C.r) continue;
        C = circ_3pp(V[i], V[j], V[k]);
      }
    }
  }
  return C;
}

Delaunay 三角剖分

// From Let it Rot. 在整数坐标同样可用
#include "2d.cpp"
using Q = struct Quad *;
const pp arb(LLONG_MAX, LLONG_MAX);
struct Quad { 
  Q rot, o; pp p = arb; bool mark; 
  pp& F() { return r() -> p; } 
  Q& r() { return rot->rot; }
  Q prev() { return rot->o->rot; } 
  Q next() { return r()->prev(); }
} *H;
ll cross(pp a, pp b, pp c) {
  return (b - a) * (c - a);
}
// p 是否在 a, b, c 外接圆中
bool incirc(pp p, pp a, pp b, pp c) { 
  i80 p2 = p.norm(), A = a.norm() - p2, B = b.norm() - p2, C = c.norm() - p2;
  a = a - p, b = b - p, c = c - p; 
  return (a * b) * C + (b * c) * A + (c * a) * B > 0;
}
Q link(pp orig, pp dest) { 
  Q r = H ? H : new Quad{new Quad{new Quad{new Quad{0}}}}; 
  H = r -> o; r -> r() -> r() = r; 
  for(int i = 0;i < 4;++ i) 
    r = r -> rot, r -> p = arb,
    r -> o = i & 1 ? r : r -> r();
  r -> p = orig, r -> F() = dest;
  return r;
}
void splice(Q a, Q b) { 
  swap(a -> o -> rot -> o, b -> o -> rot ->o); 
  swap(a -> o, b -> o);
}
Q conn(Q a, Q b) { 
  Q q = link(a -> F(), b -> p); 
  splice(q, a -> next()); 
  splice(q -> r(), b); 
  return q;
}
pair<Q, Q> rec(const vector<pp> &s){ 
  int N = size(s); 
  if(N <= 3) { 
    Q a = link(s[0], s[1]), b = link(s[1], s.back()); 
    if(N == 2) return {a, a -> r()}; 
    splice(a -> r(), b);
    ll side = cross(s[0], s[1], s[2]); 
    Q c = side ? conn(b, a) : 0; 
    return { side < 0 ? c -> r() : a, side < 0 ? c : b -> r() }; 
  }
#define H(e) e -> F(), e -> p
#define valid(e) (cross(e->F(), H(base)) > 0) 
  int half = N / 2; 
  auto [ra, A]=rec({s.begin(), s.end()-half}); 
  auto [B, rb]=rec({s.end() - half, s.end()}); 
  while((cross(B -> p, H(A)) < 0 && (A = A -> next())) || (cross(A -> p, H(B)) > 0 && (B = B -> r() -> o))); 
  Q base = conn(B -> r(), A); 
  if(A -> p == ra -> p) ra = base -> r(); 
  if(B -> p == rb -> p) rb = base;
#define DEL(e, init, dir) \
  Q e = init -> dir; \
  if(valid(e)) \
    for(;incirc(e -> dir -> F(), H(base), e -> F());) { \
      Q t = e -> dir; \
      splice(e, e -> prev()); \
      splice(e -> r(), e -> r() -> prev()); \
      e -> o = H, H = e, e = t; \
    }
  for(;;) { 
    DEL(LC, base -> r(), o); 
    DEL(RC, base, prev()); 
    if(!valid(LC) && !valid(RC)) break; 
    if(!valid(LC) || (valid(RC) && incirc(H(RC), H(LC))))
      base = conn(RC, base -> r()); 
    else 
      base = conn(base -> r(), LC -> r()); 
  }
  return {ra, rb};
}
 // 返回若干逆时针三角形
vector<pp> deluanay(vector<pp> a){
  if((int)size(a) < 2) return {};
  sort(a.begin(), a.end()); // unique
  Q e = rec(a).first;
  vector<Q> q = {e};
  while(cross(e -> o -> F(), e -> F(), e -> p) < 0) e = e -> o;
  #define ADD { Q c = e; do { c -> mark = 1; a.push_back(c -> p); q.push_back(c -> r()), c = c -> next(); } while(c != e); } 
  ADD; a.clear();
  for(int qi = 0;qi < size(q);)
    if(!(e = q[qi++]) -> mark) ADD; 
  return a;
}

动态凸包(不支持删除)

#include "2d.cpp"
struct Hull {
  set<pp> S;
  using Iter = set<pp>::iterator;
  long long ans = 0;
  long long area(pp a, pp b, pp c) {
    return llabs((b - a) * (c - a));
  }
  Iter remove_dir(Iter it, bool dir, pp x){
    Iter nxt;
    while (dir ? (it != S.begin() && (nxt = prev(it), 1)) : ((nxt = next(it)) != S.end())) {
      if (((*it - *nxt) * (x - *it) < 0) == dir) break;
      ans += area(*nxt, *it, x), S.erase(it), it = nxt;
    }
    return it;
  }
  void insert(pp x){
    if (S.empty()){ S.insert(x); return; }
    auto r = S.lower_bound(x);
    if (r == S.end()) {
      remove_dir(prev(r), 1, x);
      S.insert(x);
    } else if (r == S.begin()) {
      remove_dir(r, 0, x);
      S.insert(x);
    } else {
      auto l = prev(r);
      if (((*r - *l) * (x - *l)) < 0)
        return; // 在凸包外侧
      ans += area(*l, *r, x);
      l = remove_dir(l, 1, x);
      r = remove_dir(r, 0, x);
      S.insert(x);
    }
  }
};

半平面交

// From Let it Rot
#include "2d-lines.cpp"
vector<pp> HPI(vector<line> vs) {
  auto cmp = [](line a, line b) {
    return paraS(a, b) ? dis(a) < dis(b)
      : ::cmp(pp(a), pp(b));
  }; 
  sort(vs.begin(), vs.end(), cmp); 
  int ah = 0, at = 0, n = size(vs); 
  vector<line> deq(n + 1); 
  vector<pp> ans(n);
  deq[0] = vs[0]; 
  for(int i = 1;i <= n;++i) { 
    line o = i < n ? vs[i] : deq[ah];
    if(paraS(vs[i - 1], o)) continue;
    // maybe <=
    while(ah < at && check(deq[at - 1], deq[at], o) < 0)
      -- at;
    if(i != n)
    while(ah < at && check(deq[ah], deq[ah + 1], o) < 0)
      ++ ah;
    if(!is_parallel(o, deq[at])) { 
      ans[at] = o & deq[at], deq[++at] = o; 
    }
  }
  if(at - ah <= 2) return {}; 
  return {ans.begin() + ah, ans.begin() + at};
}

二维线段

// From Let it Rot
#include "2d.cpp"
struct line : pp {
  db z; // a * x + b * y + c (= or >) 0 
  line() = default;
  line(db a, db b, db c): pp{a, b}, z(c){} 
  // 有向平面 a -> b 左侧区域
  line(pp a, pp b): pp(r90(b - a)), z(a * b){}
  db operator()(pp a) const { // ax + by + c
    return a % pp(*this) + z;
  }
  line vertical() const {
    return {y, -x, 0};
  } // 过 O 的垂直线
  line parallel(pp o) {
    return {x, y, z - this -> operator()(o)};
  } // 过 O 的平行线
};
pp operator & (line x, line y) { // 求交
  return pp{
    pp{x.z, x.y} * pp{y.z, y.y},
    pp{x.x, x.z} * pp{y.x, y.z}
  } / -(pp(x) * pp(y));
} // 注意此处精度误差较大,res.y 需要较高精度
pp project(pp x, line l){   // 投影
  return x - pp(l) * (l(x) / l.norm());
}
pp reflact(pp x, line l){   // 对称
  return x - pp(l) * (l(x) / l.norm()) * 2;
}
db dis(line l, pp x = {0, 0}){  // 有向点距离
  return l(x) / l.abs();
}
bool is_parallel(line x, line y){ // 判断平行
  return equal(pp(x) * pp(y), 0);
}
bool is_vertical(line x, line y){ // 判断垂直
  return equal(pp(x) % pp(y), 0);
}
bool online(pp x, line l) {     // 判断点在线
  return equal(l(x), 0);
}
int ccw(pp a, pp b, pp c) { 
  int s = sign((b - a) * (c - a)); 
  if(s == 0) { 
    if(sign((b - a) % (c - a)) == -1)
      return 2; 
    if((c - a).norm() > (b - a).norm() + EPS)
      return -2; 
  }
  return s;
}
db det(line a, line b, line c) {
  pp A = a, B = b, C = c; 
  return c.z * (A * B) + a.z * (B * C) + b.z * (C * A);
}
db check(line a, line b, line c) { // sgn same as c(a & b), 0 if error
  return sign(det(a, b, c)) * sign(pp(a) * pp(b));
}
bool paraS(line a, line b) { // 射线同向
  return is_parallel(a, b) && pp(a)%pp(b) > 0;
}

Voronoi 图

// From Let it Rot
#include "2d-lines.cpp"
vector<line> cut(const vector<line> & o, line l) { 
  vector<line> res;
  int n = size(o);
  for(int i = 0;i < n;++i) {
    line a = o[i], b = o[(i + 1) % n], c = o[(i + 2) % n]; 
    int va = check(a, b, l), vb = check(b, c, l); 
    if(va > 0 || vb > 0 || (va == 0 && vb == 0))
      res.push_back(b); 
    if(va >= 0 && vb < 0) 
      res.push_back(l); 
  }
  return res.size() <= 2 ? vector<line>{}:res;
} // 切凸包
line bisector(pp a, pp b) {return line(a.x - b.x, a.y - b.y, (b.norm() - a.norm()) / 2); }
vector<vector<line>> voronoi(vector<pp> p) { 
  int n = p.size(); auto b = p;
  shuffle(b.begin(), b.end(), MT); 
  const db V = 1e5; // 边框大小,重要
  vector<vector<line>> a(n, { 
    { V, 0, V * V}, {0,  V, V * V},
    {-V, 0, V * V}, {0, -V, V * V},
  });
  for(int i = 0;i < n;++i) { 
    for(pp x : b) if((x - p[i]).abs() > EPS){ 
      a[i] = cut(a[i], bisector(p[i], x)); 
    }
  }
  return a;
}

二维基础

#include "../header.cpp"
const db EPS = 1e-9, PI  = acos(-1);
bool equal(db a, db b){ return fabs(a - b) < EPS; }
int sign(db a){ if(equal(a, 0)) return 0; return a > 0 ? 1 : -1;}
db sqr(db x) { return x * x; }
struct v2{   // 二维向量
  db x, y;
  v2(db _x = 0, db _y = 0) : x(_x), y(_y){}
  db norm() const {return (x * x + y * y);}
  db abs() const {return sqrt(x * x + y * y);}
  db arg() const {return atan2(y, x); }
};
bool operator ==(v2 a, v2 b){
  return a.x == b.x && a.y == b.y;
}
bool operator < (v2 a, v2 b){
    return a.x == b.x ? a.y < b.y : a.x < b.x;
}
v2 r90(v2 x) { return {-x.y, x.x}; }
v2 operator +(v2 a, v2 b){
  return {a.x + b.x, a.y + b.y}; }
v2 operator -(v2 a, v2 b){
  return {a.x - b.x, a.y - b.y}; }
v2 operator *(v2 a, db w){
  return {a.x * w, a.y * w}; }
v2 operator /(v2 a, db w){
  return {a.x / w, a.y / w}; }
db operator *(v2 a, v2 b){  // 叉乘,b > a 为负
  return a.x * b.y - a.y * b.x; }
db operator %(v2 a, v2 b){  // 点乘
  return a.x * b.x + a.y * b.y; }
bool equal(v2 a, v2 b){
  return equal(a.x, b.x) && equal(a.y, b.y);
}
using pp = v2;
pp rotate(pp a, db t){
  db c = cos(t), s = sin(t);
  return pp(a.x * c - a.y * s, a.y * c + a.x * s);
}
db dis(pp a, pp b){
  return (a - b).abs();
}
int half(pp x){ // 为 1 则 arg >= \pi
  return x.y < 0 || (x.y == 0 && x.x <= 0);
}
// int half(pp x){return x.y < -EPS || (std::fabs(x.y) < EPS && x.x < EPS);}
bool cmp(pp a, pp b) {  // arg a < arg b
  return half(a) == half(b) ? a * b > 0 : half(b);
}

其他

笛卡尔树

#include "../header.cpp"
// Li: 左儿子;Ri: 右儿子
int n, L[MAXN], R[MAXN], A[MAXN];
void build(){
  stack <int> S; A[n + 1] = -1e9;
  for(int i = 1;i <= n + 1;++ i){
    int v = 0;
    while(!S.empty() && A[S.top()] > A[i]){
      auto u = S.top();
      R[u] = v, v  = u, S.pop();
    }
    L[i] = v, S.push(i);
  }
}

CDQ 分治

例题

给定三元组序列 \((a_i, b_i, c_i)\),求解 \(f(i) = \sum_{j} [a_j \le a_i \land b_j\le b_i \land c_j\le c_i]\)。

#include "../header.cpp"
struct Node{int id, a, b, c;}A[MAXN], B[MAXN];
bool cmp(Node a, Node b){
  return a.a == b.a ? (a.b == b.b ? (a.c == b.c ? a.c < b.c : a.id < b.id) : a.b < b.b) : a.a < b.a;
}
int K[MAXN], H[MAXN], n, m, D[MAXM];
namespace BIT{
  void modify(int x, int w);  // 加到 m
  void query(int x, int &r);
}
using BIT :: modify, BIT :: query;
void cdq(int l, int r){
  if(l != r){
    int t = l + r >> 1;
    cdq(l, t), cdq(t + 1, r);
    int p = l, q = t + 1, u = l;
    while(p <= t && q <= r){
      if(A[p].b <= A[q].b)
modify(A[p].c, 1), B[u ++] = A[p ++];
      else
query(A[q].c, K[A[q].id]), B[u ++] = A[q ++];
    }
    while(p <= t) modify(A[p].c, 1), B[u ++] = A[p ++];
    while(q <= r) query(A[q].c, K[A[q].id]), B[u ++] = A[q ++];
    up(l, t, i) modify(A[i].c, -1);
    up(l, r, i) A[i] = B[i];
  }
}
int main(){
  n = qread(), m = qread();
  up(1, n, i) A[i].id = i, A[i].a = qread(), A[i].b = qread(), A[i].c = qread();
  sort(A + 1, A + 1 + n, cmp), cdq(1, n);
  sort(A + 1, A + 1 + n, cmp);
  dn(n, 1, i){
    if(A[i].a == A[i + 1].a && A[i].b == A[i + 1].b && A[i].c == A[i + 1].c)
      K[A[i].id] = K[A[i + 1].id];
    H[K[A[i].id]] ++;
  }
  up(0, n - 1, i) printf("%d\n", H[i]);
  return 0;
}

日期公式

// 从公元 1 年 1 月 1 日经过了多少天
int getday(int y, int m, int d) { 
  if(m < 3) -- y, m += 12; 
  return (365 * y + y / 4 - y / 100 + y / 400 + (153 * (m - 3) + 2) / 5 + d - 307);
}
// 公元 1 年 1 月 1 日往后数 n 天是哪一天
void date(int n, int &y, int &m, int &d) { 
  n += 429 + ((4 * n + 1227) / 146097 + 1) * 3 / 4;
  y = (4 * n - 489) / 1461, n -= y * 1461 / 4; 
  m = (5 * n - 1) / 153,  d = n - m * 153 / 5; 
  if (--m > 12) m -= 12, ++y;
}

pbds 库

#include "../header.cpp"
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>    // 树
#include <ext/pb_ds/priority_queue.hpp> // 堆
using namespace __gnu_pbds;
// insert, erase, order_of_key, find_by_order,
// [lower|upper]_bound, join, split, size
__gnu_pbds::tree<int, null_type, less<int>,
    rb_tree_tag,
    tree_order_statistics_node_update> T;
// push, pop, top, size, empty, modify, erase,
// join 其中 modify 修改寄存器,join 合并堆
// 还可以用 rc_binomial_heap_tag
__gnu_pbds::priority_queue<int, less<int>,
    pairing_heap_tag> Q1, Q2;

Python 技巧

import random
random.normalvariate(0.5, 0.1) # 正态分布
l = [str(i) for i in range(9)] # 列表
sorted(l), min(l), max(l), len(l)
random.shuffle(l)
l.sort(key=lambda x:x ^ 1,reverse=True)
from functools import cmp_to_key
l.sort(key=cmp_to_key(lambda x,y:(y^1)-(x^1)))
from itertools import *
for i in product('ABCD', repeat=2):
    pass # AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
for i in permutations('ABCD', repeat=2):
    pass # AB AC AD BA BC BD CA CB CD DA DB DC
for i in combinations('ABCD', repeat=2):
    pass # AB AC AD BC BD CD
for i in combinations_with_replacement('ABCD', repeat=2):
    pass # AA AB AC AD BB BC BD CC CD DD
from fractions import Fraction 
a.numerator, a.denominator, str(a)
a = Fraction(0.233).limit_denominator(1000)
from decimal import Decimal, getcontext, FloatOperation
getcontext().prec = 100
getcontext().rounding = getattr(decimal, 'ROUND_HALF_EVEN')
# default; other: FLOOR, CELILING, DOWN, ...
getcontext().traps[FloatOperation] = True
Decimal((0, (1, 4, 1, 4), -3)) # 1.414
a = Decimal(1<<31) / Decimal(100000)
print(f"{a:.9f}") # 21474.83648
print(a.sqrt(), a.ln(), a.log10(), a.exp(), a ** 2)
a = 1-2j
print(a.real, a.imag, a.conjugate())
import sys, atexit, io
_INPUT_LINES = sys.stdin.read().splitlines()
input = iter(_INPUT_LINES).__next__
_OUTPUT_BUFFER = io.StringIO()
sys.stdout = _OUTPUT_BUFFER
@atexit.register
def write():
    sys.__stdout__.write(_OUTPUT_BUFFER.getvalue())

快速测试

export CXXFLAGS='-g -Wall -fsanitize=address,undefined -Dzwb -std=gnu++20'
mk() { g++ -O2 -Dzwb -std=gnu++20 $1.cpp -o $1; }
ulimit -s 1048576
ulimit -v 1048576

自适应辛普森

#include "../header.cpp"
db simpson(db (*f)(db), db l, db r){
  return (r - l) / 6 *
    (f(l) + 4 * f((l + r) / 2) + f(r));
}
db adapt_simpson(db (*f)(db), db l, db r, db EPS, int step){
  db mid = (l + r) / 2;
  db w0 = simpson(f, l, r), w1 = simpson(f, l, mid), w2 = simpson(f, mid, r);
  return fabs(w0 - w1 - w2) < EPS && step < 0?
    w1 + w2 :
    adapt_simpson(f, l, mid, EPS, step - 1) +
    adapt_simpson(f, mid, r, EPS, step - 1);
}

对拍脚本

while true; do
  ./gen > 1.in; ./naive < 1.in > std.out; ./a < 1.in > 1.out
  if diff 1.out std.out; then echo ac; else echo wa; break fi
done

伪随机生成

#include "../header.cpp"
u32 xorshift32(u32 &x){ return
  x ^= x << 13, x ^= x >> 17, x ^= x << 5; }
u64 xorshift64(u64 &x){ return
  x ^= x << 13, x ^= x >> 7, x ^= x << 17; }