icpclib - arosh/arosh.github.com GitHub Wiki

むンストヌルするもの

$ sudo apt-get install build-essential vim-nox gedit g++ clang++ valgrind gdb

CapsLockの挙動を倉曎

# CapsLockキヌを远加のCtrlキヌずしお䜿う
$ dconf write /org/gnome/desktop/input-sources/xkb-options "['ctrl:nocaps']"
# CapsLockキヌの動䜜をデフォルトに戻す
$ dconf reset /org/gnome/desktop/input-sources/xkb-options

UbuntuTips/Desktop/HowToSetCapsLockAsCtrl - Ubuntu Japanese Wiki

.vimrc

set nu              	" number
syntax on
filetype plugin indent on
set ts=2 sw=2 sts=2 et  " tabstop shiftwidth softtabstop expandtab
set is hls ic       	" incsearch hlsearch ignorecase
set sm mat=0        	" showmatch matchtime=0

" BackSpaceでなんでも消す (たぶん䞍芁)
set backspace=indent,eol,start

Makefile

CXXFLAGS=-D_GLIBCXX_DEBUG -std=c++11 -Wall -Wextra -Wshadow -g -fsanitize=address
# 速床が遅いずきは-D_GLIBCXX_DEBUGを倖しお-O2を付ける
# -O1以䞊のずきはツヌルによっおは-fno-omit-frame-pointerが必芁

その他有甚なオプション集 1 2 3 4

Vimの操䜜

:make ビルドする
:cn 次の゚ラヌ箇所に移動
:cc ゚ラヌを再確認

Segmentation Faultが起こったずき

$ g++ -g ... # デバッグシンボルを付けおコンパむル
$ gdb ./main
(gdb) r < a.in > a.out # 実行
(gdb) bt               # backtraceを調べる
(gdb) f 3ずか4ずか     # 関数フレヌムの移動
(gdb) l                # 呚蟺の゜ヌスコヌドの衚瀺
(gdb) p x              # 倉数の衚瀺

ありがちな原因

  • メモ化再垰のキャッシュの参照時に範囲倖を参照しおいる (assert入れずけ)
  • スタックオヌバヌフロヌ (巚倧な静的倉数, 再垰しすぎ (shared_ptrのデストラクタにも泚意), ulimit -sしよう)

基本

template <class T, class U>
T lexical_cast(const U& from) {
    T to;
    stringstream ss;
    ss << from; ss >> to;
    return to;
}

// iostreamが遅いずきは std::ios::sync_with_stdio(false); を蚭定し
// std::endl の代わりに '\n' を䜿う (#define endl '\n')
// std::cin.tie(nullptr); はあたり効果がないようが気がする

printf("%0*d", 4, 123); // => 0123
cout << setw(4) << setfill('0') << 123 << endl; // => 0123

cout.setf(ios::fixed); cout.precision(10);

STL

// remove, unique, rotate
vector<int> v = { 1, 1, 2, 3, 5 };
v.erase(remove(v.begin(), v.end(), 3), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
v.erase(v.begin() + 2); // v[2]だけ消す (v.end()䞍芁)
rotate(v.begin(), v.begin() + 2, v.end()); // v[2]が最初に来る

// mapやsetの比范関数
struct Node {
    int fst, snd;
    Node(int f, int s) : fst(f), snd(s) { }
};

struct NodeCmp {
    bool operator()(const Node& a, const Node& b) {
        if(a.fst != b.fst) return a.fst < b.fst;
        return a.snd < b.snd;
    }
};

set<Node, NodeCmp> s;

// map#[]にmap#sizeはダメ。れッタむ。
std::map<int, int> dict;
dict[123] = dict.size(); // dict[123] = 0 or 1

文字列の基本操䜜

vector<string> splitAll(string s, string t) {
  vector<string> v;
  for(int p = 0; (p = s.find(t)) != string::npos; ) {
    v.push_back(s.substr(0, p));
    s = s.substr(p + t.size());
  }
  v.push_back(s);
  return v;
}

string replaceAll(string s, string f, string t) {
  string r;
  for(int p = 0; (p = s.find(f)) != string::npos; ) {
    r += s.substr(0, p) + t;
    s = s.substr(p + f.size());
  }
  return r + s;
}

ビット操䜜

__builtin_popcount(b); // (1 << 1) | (1 << 3) => 2
// 0を入れるずキケン
__builtin_ctz(b);      // (1 << 1) | (1 << 3) => 1
31 - __builtin_clz(b); // (1 << 1) | (1 << 3) => 3

// unsigned long long な堎合は
__builtin_popcountll();
__builtin_ctzll();
__builtin_clzll();

// 巊シフトの穎埋めは0であるこずが保蚌される
// unsignedの右シフトの穎埋めは0であるこずが保蚌される
// signedの右シフトの穎埋めは0であるこずが保蚌されない

S = S & ~(1 << k); // k番目のビットの消去
S = S & -S; // 最も右の1ビット
S = -S & (S + 1); // 最も右の0ビット

// 集合の郚分集合の列挙、サむズkの集合の列挙 → 蟻本のp.144に茉っおるけどよく分からん

二分探玢

  • 条件を満たす最倧倀 
 [lb, ub) 
 assert(check(lb) && !check(ub))
  • 条件を満たす最小倀 
 (lb, ub] 
 assert(!check(lb) && check(ub))
int search_max() {
  int lb = 0, ub = N;
  while(ub - lb > 1) {
    int mid = (lb + ub) / 2;
    (check(mid) ? lb : ub) = mid;
  }
  return lb;
}
int search_min() {
  int lb = -1, ub = N - 1;
  while(ub - lb > 1) {
    int mid = (lb + ub) / 2;
    (check(mid) ? ub : lb) = mid;
  }
  return ub;
}

敎数䞉分探玢

階差数列を䜜り笊号が逆転する䜍眮を探せばよいらしい (参考)

デヌタ構造

UnionFind

struct UnionFind {
  vector<int> data;
  UnionFind(int v) : data(v, -1) {}
  int root(int x) {
    return data[x] < 0 ? x : data[x] = root(data[x]);
  }
  bool same(int x, int y) {
    return root(x) == root(y);
  }
  int size(int x) {
    return -data[root(x)];
  }
  void merge(int x, int y) {
    x = root(x), y = root(y);
    if (x != y) {
      if (size(y) > size(x))
        swap(x, y);
      data[x] += data[y];
      data[y] = x;
    }
  }
};

BinaryIndexTree

  • 区間の和、および倀の曎新が O(log n) で行える
  • 節点はすべおの倀の合蚈を持぀こずになるので、桁あふれしないかどうか怜蚎せよ (typedefしたほうがいいかも)
  • 蟻本の実装は閉区間だが、この実装は半開区間である
struct BIT {
  int N;
  vector<int> data;
  BIT(int n) : N(n), data(n + 1, 0) { }
  void add(int a, int w) {
    ++a;
    for(int x = a; x <= N; x += x & -x) {
      data[x] += w;
    }
  }
  int sum(int a) {
    int ret = 0;
    for(int x = a; x > 0; x -= x & -x) {
      ret += data[x];
    }
    return ret;
  }
};

区間に察するク゚リ

add(l, r, x) // [l,r)にxを足す
bit0.add(l, -x * l);
bit1.add(l, x);
bit0.add(r, x * r);
bit1.add(r, -x);
sum(l, r) // [l, r)の和を蚈算
bit0.sum(r) + r*bit1.sum(r) - bit0.sum(l) - l*bit1.sum(l);

RangeMinimumQuery

  • 節点の数はN-1個
  • 初期倀をどうするか怜蚎しよう(範囲倖の察凊をするのが嫌なので)
const int INF = (int)1e9;
struct RMQ {
    vector<int> data;
    int N;
    RMQ(int size) {
        N = 1;
        while(N < size) N *= 2;
        data.assign(2*N - 1, INF);
    }
    // 0オリゞンでk番目の芁玠をaに倉曎する
    void update(int k, int a) {
        k += N - 1; // 葉に移動
        data[k] = a;
        while(k > 0) {
            k = (k - 1)/2;
            data[k] = min(data[2*k + 1], data[2*k + 2]);
        }
    }
    // minimum [a, b)
    int minimum(int a, int b) {
        return minimum_sub(a, b, 0, 0, N);
    }
    int minimum_sub(int a, int b, int k, int l, int r) {
        if(r <= a || b <= l) return INF;
        if(a <= l && r <= b) return data[k];
        
        return min(minimum_sub(a, b, 2*k + 1, l, (l + r)/2),
                   minimum_sub(a, b, 2*k + 2, (l + r)/2, r));
    }
};

StarrySkyTree

struct SegTree {
    int N;
    vector<ll> data_add;
    vector<ll> data_min;
    SegTree(int size) {
        N = 1;
        while(N < size) N *= 2;
        data_add.assign(2*N - 1, 0LL);
        data_min.assign(2*N - 1, 0LL);
    }
    void add(int a, int b, ll x, int k, int l, int r) {
        if(r <= a || b <= l) return;
        if(a <= l && r <= b) {
            data_add[k] += x;
            while(k > 0) {
                k = (k - 1) / 2;
                data_min[k] = min(data_min[2*k + 1] + data_add[2*k + 1],
                                  data_min[2*k + 2] + data_add[2*k + 2]);
            }
        }
        else {
            add(a, b, x, 2*k + 1, l, (l + r)/2);
            add(a, b, x, 2*k + 2, (l + r)/2, r);
        }
    }
    ll minimum(int a, int b, int k, int l, int r) {
        if(r <= a || b <= l) return INF;
        if(a <= l && r <= b) {
            return data_min[k] + data_add[k];
        }
        else {
            return min(minimum(a, b, 2*k + 1, l, (l + r)/2),
                       minimum(a, b, 2*k + 2, (l + r)/2, r)) + data_add[k];
        }
    }
};

平方分割

  • sqrtNの調敎によっお速床が結構倉わる。2*sqrt(N)くらいが良い気がする
  • updateずqueryは、ブロックを芆うような堎合 (Large) ずブロックの内郚だけの堎合 (Small) の合蚈4通りに察応するこずを確かめるず良い
  • Nが小さなケヌスだず範囲に察する操䜜のテストが行われないのでサンプルテストのずきはsqrtN = 2ずするべき
const int sqrtN = 400;
struct SqrtDecomposition {
  int N, K;
  vector<int> data;
  vector<int> addpart;
  vector<int> addall;
  SqrtDecomposition(int n) : N(n) {
    K = (N + sqrtN - 1) / sqrtN;
    data.assign(K * sqrtN, 0);
    addpart.assign(K, 0);
    addall.assign(K, 0);
  }
  void put(int a, int b, int value) {
    for(int k = 0; k < K; ++k) {
      int l = k * sqrtN, r = (k + 1) * sqrtN;
      if(r <= a || b <= l) continue;
      if(a <= l && r <= b) {
        addall[k] += value;
      }
      else {
        for(int i = max(a, l); i < min(b, r); ++i) {
          data[i] += value;
          addpart[k] += value;
        }
      }
    }
  }
  int get(int a, int b) {
    int ret = 0;
    for(int k = 0; k < K; ++k) {
      int l = k * sqrtN, r = (k + 1) * sqrtN;
      if(r <= a || b <= l) continue;
      if(a <= l && r <= b) {
        ret += addall[k] * sqrtN + addpart[k];
      }
      else {
        for(int i = max(a, l); i < min(b, r); ++i) {
          ret += data[i] + addall[k];
        }
      }
    }
    return ret;
  }
};

Mo's algorithm

  • 芁玠が曎新されない
  • ク゚リの先読みが可胜
  • 区間 [L,R] の結果から [L-1, R], [L+1, R], [L, R-1], [L, R+1] の結果が容易に埗られる
queries.sort(key=lambda x: (x.L // S, x.R))
i, j = 0, -1
for l, r, index in queries:
    while i < l: remove(i++)  // [i, j] -> [i+1, j]
    while i > l: add(--i)     // [i, j] -> [i-1, j]
    while j < r: add(++j)     // [i, j] -> [i, j+1]
    while j > r: remove(j--)  // [i, j] -> [i, j-1]
    ans[index] = answer

http://pekempey.hatenablog.com/entry/2016/01/23/185143 https://twitter.com/yosupot/status/745520261819031552

グラフ理論

オむラヌ閉路・ハミルトン閉路

  • すべおの蟺を1床だけ通る閉路 → オむラヌ閉路
  • すべおの頂点を1床だけ通る閉路 → ハミルトン閉路

無向グラフのオむラヌ路

  • 始点ず終点が同じ → グラフが連結で、すべおの頂点の次数が偶数
  • 始点ず終点が異なる → グラフが連結で、始点ず終点の次数が奇数で、その他は偶数

有向グラフのオむラヌ路

  • 始点ず終点が同じ → グラフが連結で、すべおの頂点に぀いお入次数ず出次数が同じ
  • 始点ず終点が異なる → 

PrimずKruskal

  • Primは最小党域朚を求めるアルゎリズム
  • Kruskalは最小党域森を求めるアルゎリズム

ダむクストラ法

struct Edge {
  int to, cost;
  Edge(int to_, int cost_) : to(to_), cost(cost_) { }
};
struct State {
  int pos, cost;
  State(int pos_, int cost_) : pos(pos_), cost(cost_) { }
  bool operator <(const State &s) const {
    return cost > s.cost;
  }
};
vector<int> shortest_path(const vector<vector<Edge>> &G, const int src) {
  const int n = G.size();
  vector<int> d(n, INF);
  priority_queue<State> Q;
  Q.emplace(src, 0);
  d[src] = 0;
  while(!Q.empty()) {
    State s = Q.top(); Q.pop();
    if(s.cost > d[s.pos]) continue;
    for(Edge e : G[s.pos]) {
      if(d[e.to] > s.cost + e.cost) {
        d[e.to] = s.cost + e.cost;
        Q.emplace(e.to, d[e.to]);
      }
    }
  }
  return d;
}

グラフの最短経路埩元

d[j] = d[i] + cost[i][k]; ず曎新された時に prev[j] = i; みたいなこずをすればよい。蟻本p.98を参照

ベルマンフォヌド法

負の蟺があっおも動䜜する。負の閉路の怜出が行える。蟻本p.95を参照。

ワヌシャル-フロむド法

  • d[i][i] = 0 にしないず動䜜しない
  • 負蟺があるずきはいろいろずダバい。G[a][b]=∞, G[a][c]=∞, G[b][c]=-1みたいなずきに G[a][c]=min(G[a][c],G[a][b]+G[b][c]) ずやるず G[a][c]<∞ ずなっおしたうので、minをずる前に G[a][b]!=∞, G[b][c]!=∞ を確認するこず。
  • d[i][i] < 0 になっおいたら → 負の閉路が存圚する

巡回セヌルスマン問題

v = 0 からはじたり、v = 0で終わるずき

const int MAX_N = 16;
const int INF = (int)1e9;

int d[MAX_N][MAX_N];
int dp[MAX_N][1 << MAX_N];

int tsp(int v, int b) {
    if(dp[v][b] != -1) return dp[v][b];
    // 党おの頂点を経由し、戻っおきた
    if(b == (1 << N) - 1 && v == 0) return 0;

    int res = INF;
    for(int i = 0; i < N; i++) {
        if(b & (1 << i)) continue;
        res = min(res, tsp(i, b | (1 << i)) + d[v][i]);
    }
    return dp[v][b] = res;
}

int solve() {
    memset(dp, -1, sizeof(dp));
    return tsp(0, 0); // 呌び出し泚意
}

匷連結成分分解

struct Scc {
    const int V; // 頂点数
    vector<vector<int> > G, revG; // グラフの隣接リスト衚珟ず、その逆グラフ
    vector<int> order; // scc()を呌ぶず、トポロゞカル順序が入る
    vector<int> vs; // 垰りがけ順序
    vector<bool> used; // STL泚意

    Scc(int v) : V(v), G(v), revG(v), order(v), used(v) { }

    void add_edge(int from, int to) {
        G[from].push_back(to);
        revG[to].push_back(from);
    }
    void dfs(int v) {
        used[v] = true;
        for(int to : G[v]) {
            if(used[to] == false) dfs(to);
        }
        vs.push_back(v);
    }
    void rdfs(int v, int k) {
        used[v] = true;
        order[v] = k;
        for(int to : revG[v]) {
            if(used[to] == false) rdfs(to, k);
        }
    }
    // @return 匷連結成分を朰しおDAGを䜜ったずきの頂点数
    int compute() {
        vs.clear();
        used.assign(V, false);
        for(int i = 0; i < V; i++) {
            if(used[i] == false) dfs(i);
        }
        int k = 0;
        reverse(vs.begin(), vs.end());
        used.assign(V, false);
        for(int v : vs) {
            if(used[v] == false) rdfs(v, k++);
        }
        return k;
    }
};

トポロゞカル゜ヌト

O(V+E)

vector<int> topological_sort(const vector<vector<int>> &G) {
  const int N = G.size();
  vector<int> indeg(N, 0);
  for(auto &&vs : G) {
    for(int v : vs) {
      indeg[v]++;
    }
  }
  queue<int> Q;
  for(int i = 0; i < N; ++i) {
    if(indeg[i] == 0) Q.push(i);
  }
  vector<int> ret;
  while(!Q.empty()) {
    int v = Q.front(); Q.pop();
    ret.push_back(v);
    for(int u : G[v]) {
      indeg[u]--;
      if(indeg[u] == 0) Q.push(u);
    }
  }
  return ret;
}

最小共通祖先

struct LCA {
  int N, logN; // logN == ceil(lg(N))
  vector<vector<int>> G;
  vector<int> depth;
  // vから根の方向に2^k回登ったずきのノヌド parent[k][v]
  vector<vector<int>> parent;

  LCA(int size) : N(size), G(size), depth(size) {
    logN = 0;
    for(int x = 1; x < N; x *= 2) logN++;
    parent.assign(max(logN, 1), vector<int>(N));
  }
  // どちらが根であっおもOK
  void add_edge(int u, int v) {
    G[u].push_back(v);
    G[v].push_back(u);
  }
  void dfs(int v, int par, int dep) {
    depth[v] = dep;
    parent[0][v] = par;
    for(int next : G[v]) {
      if(next != par) dfs(next, v, dep + 1);
    }
  }
  void build(int root) {
    dfs(root, -1, 0);
    for(int k = 1; k < logN; k++) {
      for(int v = 0; v < N; v++) {
        if(parent[k - 1][v] == -1) parent[k][v] = -1;
        else parent[k][v] = parent[k - 1][parent[k - 1][v]];
      }
    }
  }
  int lca(int u, int v) {
    if(depth[u] > depth[v]) swap(u, v); // vのほうが深くなる
    // uずvの深さが同じになるたでvを根の方向に移動する
    for(int k = 0; k < logN; k++) {
      if(((depth[v] - depth[u]) >> k) & 1) {
        v = parent[k][v];
      }
    }
    if(u == v) return u;
    for(int k = logN - 1; k >= 0; k--) {
      if(parent[k][u] != parent[k][v]) {
        u = parent[k][u];
        v = parent[k][v];
      }
    }
    return parent[0][u];
  }
};

LowLink

struct LowLink {
  const int N;
  vector<vector<int>> G;
  vector<bool> vis;
  vector<int> ord, parent, low;
  LowLink(int size) : N(size), G(size), vis(size, false), ord(size), parent(size), low(size) { }
  void add(int a, int b) {
    G[a].push_back(b); G[b].push_back(a);
  }
  void dfs(int v, int &k) {
    ord[v] = low[v] = k++;
    vis[v] = true;
    for(int u : G[v]) {
      if(!vis[u]) {
        parent[u] = v;
        dfs(u, k);
        low[v] = min(low[v], low[u]);
      }
      else if(u != parent[v]) {
        low[v] = min(low[v], ord[u]);
      }
    }
  }
  void compute() {
    int k = 0;
    dfs(0, k);
  }
  vector<pair<int, int>> bridge() {
    compute();
    vector<pair<int, int>> ret;
    for(int i = 0; i < N; ++i) {
      if(ord[parent[i]] < low[i]) ret.emplace_back(parent[i], low[i]);
    }
    return ret;
  }
  vector<int> articulation() {
    compute();
    vector<int> ret;
    int c = 0;
    for(int i = 1; i < N; ++i) {
      int p = parent[i];
      if(p == 0) ++c;
      else if(ord[p] <= low[i]) ret.push_back(p);
    }
    if(c >= 2) ret.push_back(0);
    sort(ret.begin(), ret.end());
    ret.erase(unique(ret.begin(), ret.end()), ret.end());
    return ret;
  }
};

行列ず朚

  • 自己ルヌプ倚重蟺を持たない無向グラフGにi↔jの蟺があるずきに(i,j)芁玠が1ずなる行列を隣接行列Aずする。A^nの(i,j)芁玠は長さnでi→jに行く経路数 (特に(i,i)芁玠は閉路)
  • 行列朚定理頂点iの次数を察角芁玠(i,i)に持぀行列を次数行列Dずする。ラプラシアン行列L=D-Aの任意の䜙因子は等しくその倀は党域朚の個数である (䟋)

Ford-Fulkersonのアルゎリズム

const int INF = (int)1e9;

struct Edge {
  int to, cap, rev;
  Edge(int to_, int cap_, int rev_) :
    to(to_), cap(cap_), rev(rev_) { }
};

struct FordFulkerson {
  const int V;
  vector<vector<Edge> > G;
  vector<bool> used;
  FordFulkerson(int v) : V(v), G(v), used(v) { }
  void add_edge(int from, int to, int cap) {
    G[from].emplace_back(to, cap, G[to].size());
    G[to].emplace_back(from, 0, G[from].size() - 1);
  }
  int find_flow(int v, int t, int f) {
    if(v == t) return f;
    used[v] = true;
    for(Edge &e : G[v]) {
      if(used[e.to] == false && e.cap > 0) {
        int d = find_flow(e.to, t, min(f, e.cap));
        if(d > 0) {
          e.cap -= d;
          G[e.to][e.rev].cap += d;
          return d;
        }
      }
    }
    return 0;
  }
  int max_flow(int s, int t) {
    int flow = 0;
    while(true) {
      used.assign(V, false);
      int f = find_flow(s, t, INF);
      if(f == 0) return flow;
      flow += f;
    }
  }
};

二郚マッチング

struct Matching {
  int N, M;
  vector<vector<int>> G;
  vector<int> match;
  vector<bool> used;
  Matching(int n, int m) : N(n), M(m), G(n + m) {}
  void add_edge(int u, int v) {
    G[u].push_back(v);
    G[v].push_back(u);
  }
  bool dfs(int v) {
    used[v] = true;
    for (int u : G[v]) {
      int w = match[u];
      if (w < 0 || (!used[w] && dfs(w))) {
        match[v] = u;
        match[u] = v;
        return true;
      }
    }
    return false;
  }
  int max_match() {
    int res = 0;
    match.assign(N + M, -1);
    for (int v = 0; v < N + M; ++v) {
      if (match[v] < 0) {
        used.assign(N + M, false);
        if (dfs(v)) res++;
      }
    }
    return res;
  }
};

最小費甚流

割り圓お問題 → ゜ヌトするだけだったりしたせんか

牛ゲヌ

ずこはるさんの「LPずグラフず定匏化」を参照

  1. 最短経路問題を「䜿う蟺の重みの合蚈を最小化」するIPずしお定矩する
  2. 頭の良い緩和を導入しおLPにする
  3. 双察をずるず牛の問題になる

あんたり圹に立たなさそうだが䞀応暙準圢を茉せおおく

  • 䞻問題 max. c'x, s.t. (1) Ax <= b, (2) x >= 0
  • 双察問題 min. b'y, s.t. (3) A'y >= c, (4) y >= 0

構文解析

  • 本圓にLL(1) 時間があるならBNFは曞いたほうが良いですよ
  • 右から読むずLL(1)のパタヌン
typedef string::const_iterator iter;
int expr(iter& p);
int term(iter& p);
int factor(iter& p);
int number(iter& p);
int expr(iter& p) {
    int r = term(p);
    while(true) {
        if(*p == '+') {
            ++p;
            int rs = term(p);
            r += rs;
        }
        else if(*p == '-') {
            ++p;
            int rs = term(p);
            r -= rs;
        }
        else {
            break;
        }
    }
    return r;
}
int term(iter& p) {
    int r = factor(p);
    while(true) {
        if(*p == '*') {
            p++;
            int rs = factor(p);
            r *= rs;
        }
        else if(*p == '/') {
            p++;
            int rs = factor(p);
            r /= rs;
        }
        else {
            break;
        }
    }
    return r;
}
int factor(iter& p) {
    if(*p == '(') {
        ++p; // skip (
        int r = expr(p);
        ++p; // skip )
        return r;
    }
    return number(p);
}
int number(iter& p) {
    int r = 0;
    while(isdigit(*p)) {
        r *= 10;
        r += *p -'0';
        p++;
    }
    return r;
}

ロヌリングハッシュ

  • BASE ... 29, 53, 131, 257
  • MOD ... 636094623231363827, 348051774975651917, 140814840257324803, 71777214294589669

サむコロ

enum FACE { TOP, FRONT, RIGHT, LEFT, BACK, BOTTOM }; // 1..6
typedef array<int, 6> Dice;
FACE tbl[6][4] = {
  { TOP, BACK, BOTTOM, FRONT },
  { TOP, RIGHT, BOTTOM, LEFT },
  { TOP, FRONT, BOTTOM, BACK },
  { TOP, LEFT, BOTTOM, RIGHT },
  { FRONT, RIGHT, BACK, LEFT },
  { FRONT, LEFT, BACK, RIGHT }
};
enum MOVE { TOP_TO_BACK, TOP_TO_RIGHT, TOP_TO_FRONT,
            TOP_TO_LEFT, FRONT_TO_RIGHT, FRONT_TO_LEFT };
void rotate(Dice &d, FACE t[4]) {
  int tmp = d[t[3]];
  d[t[3]] = d[t[2]]; d[t[2]] = d[t[1]]; d[t[1]] = d[t[0]]; d[t[0]] = tmp;
}
vector<Dice> generate_all(Dice dice) {
  vector<Dice> res;
  for(int i = 0; i < 6; ++i) {
    for(int j = 0; j < 4; ++j) {
      res.emplace_back(dice);
      rotate(dice, tbl[FRONT_TO_RIGHT]);
    }
    if(i % 2 == 0) rotate(dice, tbl[TOP_TO_BACK]);
    else rotate(dice, tbl[TOP_TO_RIGHT]);
  }
  return res;
}

線圢代数

ケむリヌ・ハミルトンの定理

正方行列Aの特性倚項匏をg(t)ずしたずき g(A)=O

連立䞀次方皋匏の解の存圚条件

  • if rank(A) = rank(A|b) = n → 唯䞀解をも぀
  • elif rank(A) = rank(A|b) → 解を耇数も぀
  • else → 解なし

掃き出し法

  • rank = 掃き出しが行えた回数
  • あんたり行列に぀いお深く考えず、単に連立方皋匏ずしお考えたほうが良い。
  • 関心の無い郚分の解に぀いおは解が䞀様に定たるように適圓に係数を入れる
  • 単なるDPで解けないか 反埩法による嘘解法を投げおみる
bool EQ(double a, double b) { return abs(a - b) < 1e-8; }
typedef vector<double> Vec;
typedef vector<Vec> Mat;

bool solve_mat(Mat B, Vec& x) {
    const int N = B.size();

    for(int i = 0; i < N; i++) {
        int pivot = i;
        for(int r = i + 1; r < N; r++) {
            if(abs(B[r][i]) > abs(B[pivot][i])) pivot = r;
        }
        B[i].swap(B[pivot]);

        if(EQ(B[i][i], 0)) return false;
        // 本来ならば0や1になる芁玠の蚈算をサボる
        for(int j = i + 1; j < N + 1; j++) B[i][j] /= B[i][i];
        for(int r = 0; r < N; r++) {
            if(r == i) continue;
            double mul = B[r][i];
            for(int j = i + 1; j < N + 1; j++) {
                B[r][j] -= B[i][j] * mul;
            }
        }
    }

    x.resize(N);
    for(int i = 0; i < N; i++) x[i] = B[i][N];
    return true;
}

行列匏

「行の入れ替えは-1倍」に気を぀けながら基本倉圢で䞉角化しお察角芁玠の積をずる

数倀蚈算

シンプ゜ン法

double simpson(double L, double R, int div = 1) {
  const double h = (R - L) / (2 * div);
  double s = 0.0, t = 0.0;
  for(int i = 1; i <= 2 * div - 3; i += 2) {
    s += f(L + h*i); t += f(L + h*(i + 1));
  }
  return h / 3 * (f(L) + f(R) + 4*(s + f(R - h)) + 2*t);
}

æ•°å­Š

玠数

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

互いに玠

  • aずbが互いに玠 := gcd(a,b)=1
  • 敎数a,bが互いに玠であれば ax+by=1 を満たす敎数x,yが存圚する。ax+by=gcd(a,b) を満たすa,bはextgcdで求められる
  • aずb1が互いに玠 && aずb2が互いに玠 => aずb1*b2は互いに玠
  • aずbが互いに玠 <=> 2a-1ず2b-1が互いに玠
  • 連続する2぀の敎数は互いに玠 ∵gcd(n,n+1)=gcd(n,(n+1)%n)=gcd(n,1)=1

逆元

ax≡1 (mod m)を満たすxをaの逆元ずいう。ax=1 (mod m)のずきax=1+kmを満たすkが存圚するのでax-km=1ずなるxkを探せば良い。

MODが玠数ならinv(n)=pow(n,m-2) (フェルマヌの小定理)

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

int mod_inv(int a, int m) {
  int x, y;
  extgcd(a, m, x, y);
  return (m + x % m) % m;
}

フェルマヌの小定理

pが玠数、pずnが互いに玠であるずき、n^(p-1)=1 (mod p)

オむラヌのφ関数

φ(n) := i <- 1..n に぀いお gcd(n,i)==1 の個数

TODO: どちらかずいうずO(√n)の実装のほうがほしい  (蟻本p.261)

vector<int> euler(int n) {
    vector<int> res(++n);
    for(int i = 0; i < n; i++) res[i] = i;
    for(int i = 2; i < n; i++) {
        if(res[i] == i) {
            for(int j = i; j < n; j += i)
                res[j] = res[j]/i*(i-1);
        }
    }
    return res;
}

べき剰䜙

x^k % m を蚈算する。m^2 < INT_MAX ならば桁あふれしない

ll pow_mod(ll x, ll y) {
  ll res = 1;
  for(; y > 0; y >>= 1, (x *= x) %= MOD) {
    if(y & 1) (res *= x) %= MOD;
  }
  return res;
}

最倧公玄数

ll lcm_many(const vector<ll> &v) {
  vector<int> maxexp(MAX + 1, 0);
  for(ll item : v) {
    ll s = item;
    for(ll d = 2; d * d <= s; d++) {
      int k = 0;
      while (s % d == 0) {
        s /= d;
        k++;
      }
      maxexp[d] = max(maxexp[d], k);
    }
    maxexp[s] = max(maxexp[s], 1);
  }
  ll lcm = 1;
  for (ll i = 2; i <= MAX; ++i) {
    lcm *= pow_mod(i, maxexp[i]);
    lcm %= MOD;
  }
  return lcm;
}

ll lcm1ton(const int n) {
  ll lcm = 1;
  vector<bool> vis(n + 1, false);
  for(int i = 2; i <= n; ++i) {
    if(vis[i]) continue;
    ll num = i;
    while(num * i <= n) num *= i;
    for(int j = i * i; j <= n; j += i)
      vis[j] = true;
    (lcm *= num) %= MOD;
  }
  return lcm;
}

組み合わせ

const int MAX_N = 10;
const int MAX_K = 10;
int dp[MAX_N][MAX_K];

int comb(int n, int k) {
    if(k < 0 || k > n) return 0;
    if(n == 0) return 1;
    if(dp[n][k] > 0) return dp[n][k];

    return dp[n][k] = comb(n-1, k-1) + comb(n-1, k);
}

int main() {
    memset(dp, 0, sizeof(dp));
}

Stern-Brocot朚

小さい順に分数を列挙する

void stern_brocot(int ax = 0, int ay = 1, int bx = 1, int by = 0) {
    int mx = ax + bx; // 分子
    int my = ay + by; // 分母
    // 䜕らかの制限をかける
    stern_brocot(ax, ay, mx, my);
    // 䜕かする
    stern_brocot(mx, my, bx, by);
}

期埅倀

  • E[X] = sum(x_i*p_i)
  • 期埅倀の和は和の期埅倀 E[X+Y] = E[X] + E[Y]
  • XずYが独立な事象の確率倉数なら E[X*Y] = E[X]*E[Y]

Nim

石の山がN個あり、それぞれA[i]個の石を含んでいたす。AliceずBobは亀互に空でない山を1぀遞び、そこから1぀以䞊の石をずりたす。Aliceの番からはじたり、最埌の石を取ったほうが勝ちです。䞡者が最善を尜くした堎合、勝぀のはどちらですか

  • A[1] ^ A[2] ^ ... A[N] != 0 → 先手 (Alice) の勝ち
  • A[1] ^ A[2] ^ ... A[N] == 0 → 埌手 (Bob) の勝ち

逆型

å…žåž‹DP集

  • 分割数: N個の区別ができない品物をN個以䞋のグルヌプ(順番は考えない)に分けるずきの分け方の数 (解説)
  • カタラン数: N+1個の葉を持぀二分朚の䜜り方の総数
  • dp[i+1][j]=Σ_{k=a}^b dp[i][j-k]みたいな圢匏でO(NM^2)のずきは环積和でO(NM)に萜ずせるこずが倚い

その他

  • 20! < 2^63
  • 4*4000*4000(B) = 6.4(MB)
  • ラむブラリを持っおいない幟䜕 → 愚盎に方皋匏をたおおみるずうたくいくかも
  • 䜕らかの制玄が぀いた文字列なり数列 → オヌトマトン
  • いもす法、しゃくずり法、priority_queue、凞、単調増加、単調枛少

グラフ関係

  • 問題をよく読むず → 二郚グラフ、朚、DAG
  • 二郚グラフ (蟻本p.198)
    • |最小蟺カバヌ(すべおの頂点に接続する蟺の遞び方)| = |V| - |最倧マッチング|
    • |最小点カバヌ(すべおの蟺に接続する頂点の遞び方)| = |最倧マッチング|
    • |最倧安定集合| = |V| - |最倧マッチング|
  • 朚 → LCA、オむラヌツアヌ (配列 or 二次元プロット)、ダブリング、HL分解 (参考)
  • DAG → トポロゞカル゜ヌト、匷連結成分分解、ベルマンフォヌドで最長経路

数孊関係

  • 確率、期埅倀
    • DP
    • 線圢性 (重み*確率 
 定矩に則っお手で蚈算しおみるこず
    • 挞化匏を連立䞀次方皋匏にする (ランダムりォヌクのように)
    • nCmやnPmやn!を䜿っお蚈算しなくおも解ける or 䜿わないほうが簡単な問題のほうが倚い
    • 状態遷移図を曞くべし
  • 敎数論
    • 玠因数分解(倍数ずか玄数ずいった蚀葉に泚目)、GCD、LCM、埪環
    • 「nがmで割り切れる」はn=kmたたはn/m=k「nをmで割った䜙りがa」はn=km+aなどずおく

バグった時

  • 1-indexed, 0-indexed, (i, j) <-> (x, y) or (y, x)
  • 開区間, 閉区間
  • INFが倧きすぎる or 小さすぎる
  • グラフ
    • 倚重蟺、自己ルヌプ、非連結
  • 誀差
    • -0.000
    • 1.0/n <=> 1.0/m → 1.0*m <=> 1.0*n に倉圢
  • 幟䜕
    • 線分は端を含むか
    • 䞊行, 同䞀の盎線
    • 「図圢」が1点たたは2点で構成されおいる時に特別な凊理が必芁
⚠ **GitHub.com Fallback** ⚠