diff --git a/AGENTS.md b/AGENTS.md index 3ac793a..29b0d04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ - **그 외 모든 식별자**는 **소문자 `snake_case`** 사용 (STL 스타일): - struct, class, namespace, function, variable, file name - 예시: `struct fenwick_tree`, `namespace fast_io`, `solve_case`, `add_edge` -- **간결하지만 설명적**: 이름은 타이핑 속도를 위해 최대 7자 이내로 짧아야 함. 동시에 압박 속에서도 읽을 수 있을 정도로는 명확해야 함. +- **간결하지만 설명적**: 이름은 타이핑 속도를 위해 최대 12자 이내로 짧아야 함. 동시에 압박 속에서도 읽을 수 있을 정도로는 명확해야 함. - **Good**: `cnt`, `idx`, `res`, `nxt`, `vis`, `dist`. - **Bad**: `number_of_elements`, `adjacency_list`, `calculated_distance`. diff --git a/scripts/test.sh b/scripts/test.sh index 1287b31..0611a6d 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -7,7 +7,7 @@ cd "$root_dir" out_dir=$(mktemp -d) trap 'rm -rf "$out_dir"' EXIT -for src in tests/1-ds/test_*.cpp; do +for src in tests/1-ds/test_*.cpp tests/2-graph/test_*.cpp; do bin="$out_dir/$(basename "${src%.cpp}")" echo "[build] $src" g++ -std=c++17 -O2 -pipe "$src" -o "$bin" diff --git a/src/2-graph/bcc.cpp b/src/2-graph/bcc.cpp index 8f59e69..3993d16 100644 --- a/src/2-graph/bcc.cpp +++ b/src/2-graph/bcc.cpp @@ -1,69 +1,70 @@ #include "../common/common.hpp" -// A Biconnected Component (BCC) is a subset of vertices in an undirected graph that satisfies the following conditions: -// (1) If you delete any vertex from a subset, the remaining vertices are connected to each other. -// (2) Adding other vertices to this subset does not satisfy (1). (This is the largest set that satisfies (1)) -// TIME COMPLEXITY: O(V + E) +// what: biconnected components + articulation points/edges (undirected). +// time: O(n+m); memory: O(n+m) +// constraint: 1-indexed; no self-loops; recursion depth O(n). +// usage: bcc g; g.init(n); g.add(u,v); g.run(); // g.bccs, g.ap, g.ae +struct bcc { + int n, tim; + vector> adj; + vector dfn, low, ap, st; + vector ed, ae; + vector> bccs; -// A vertex at which the graph is divided into two or more components when the vertex is removed is called a 'articulation point'. -// After decomposing the graph into BCCs, vertices belonging to two or more BCCs are articulation point. -// Similarly, a edge at which the graph is divided into two or more components when the edge is removed is called a 'articulation edge'. -// For all tree edges on a dfs spanning tree, if tmp > dfsn[now], the edge { now, next } is a articulation edge. - -const int MAXV = 101010; -int n, m; -vector adj[MAXV]; -vector> bcc; -set aPoint; -set aEdge; -void input() { - cin >> n >> m; - for (int i = 0; i < m; i++) { - int u, v; - cin >> u >> v; - adj[u].push_back(v); - adj[v].push_back(u); + void init(int n_) { + n = n_; + tim = 0; + adj.assign(n + 1, {}); + dfn.assign(n + 1, -1); + low.assign(n + 1, 0); + ap.clear(); + ae.clear(); + st.clear(); + ed.clear(); + bccs.clear(); + } + void add(int u, int v) { + int id = sz(ed); + ed.push_back({u, v}); + adj[u].push_back({v, id}); + adj[v].push_back({u, id}); } -} -int dfsn[MAXV], dCnt; -stack stk; -int dfs(int now, int prv) { - int ret = dfsn[now] = ++dCnt; - int childCnt = 0; - for (int next : adj[now]) { - if (next == prv) continue; - // If an edge { now, next } has not yet been visited, puts an edge on the stack. - if (dfsn[now] > dfsn[next]) stk.push({now, next}); - // Back edge - if (dfsn[next] != -1) ret = min(ret, dfsn[next]); - // Tree edge - else { - childCnt++; - int tmp = dfs(next, now); - ret = min(ret, tmp); - if (prv != -1 && tmp >= dfsn[now]) - aPoint.insert(now); - if (tmp > dfsn[now]) - aEdge.insert({min(now, next), max(now, next)}); - // If next cannot go to ancestor node of now, find BCC - if (tmp >= dfsn[now]) { - vector nowBCC; - while (true) { - pii t = stk.top(); - stk.pop(); - nowBCC.push_back(t); - if (t == make_pair(now, next)) break; + void dfs(int v, int pe) { + dfn[v] = low[v] = ++tim; + int ch = 0; + for (auto [to, id] : adj[v]) { + if (id == pe) continue; + if (dfn[to] != -1) { + // edge: back edge to ancestor. + low[v] = min(low[v], dfn[to]); + if (dfn[to] < dfn[v]) st.push_back(id); + continue; + } + st.push_back(id); + ch++; + dfs(to, id); + low[v] = min(low[v], low[to]); + if (pe != -1 && low[to] >= dfn[v]) ap.push_back(v); + if (low[to] > dfn[v]) ae.push_back({min(v, to), max(v, to)}); + if (low[to] >= dfn[v]) { + vector comp; + while (1) { + int eid = st.back(); + st.pop_back(); + comp.push_back(ed[eid]); + if (eid == id) break; } - bcc.push_back(nowBCC); + bccs.push_back(comp); } } + if (pe == -1 && ch > 1) ap.push_back(v); + } + void run() { + for (int v = 1; v <= n; v++) + if (dfn[v] == -1) dfs(v, -1); + sort(all(ap)); + ap.erase(unique(all(ap)), ap.end()); + sort(all(ae)); + ae.erase(unique(all(ae)), ae.end()); } - if (prv == -1 && childCnt > 1) - aPoint.insert(now); - return ret; -} -void getBCC() { - memset(dfsn, -1, sizeof(dfsn)); - for (int v = 1; v <= n; v++) - if (dfsn[v] == -1) dfs(v, -1); -} \ No newline at end of file +}; diff --git a/src/2-graph/dijkstra_bellman_ford_floyd_warshall.cpp b/src/2-graph/dijkstra_bellman_ford_floyd_warshall.cpp deleted file mode 100644 index b2f49c7..0000000 --- a/src/2-graph/dijkstra_bellman_ford_floyd_warshall.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "../common/common.hpp" - -// 1. Dijkstra's Algorithm -// TIME COMPLEXITY: O(ElogV) -vector> adj[202020]; -vector dist(202020, (ll)1e18); -void dijkstra(int st) { - priority_queue, vector>, greater>> pq; - pq.push({0, st}); - dist[st] = 0; - while (!pq.empty()) { - auto [w, v] = pq.top(); - pq.pop(); - if (w > dist[v]) continue; - for (auto &i : adj[v]) { - if (dist[i.v] > w + i.w) { - dist[i.v] = w + i.w; - pq.push({w + i.w, i.v}); - } - } - } -} - -// 2. Bellman-Ford Algorithm -// INPUT: Given a directed graph with weighted(possibly negative) edges and no negative cycles. Given a starting vertex. -// OUTPUT: Outputs the shortest distance from the starting vertex to all vertices. -// TIME COMPLEXITY: O(VE) -const ll INF = 1e18; -int n, m; -vector> adj[101010]; -vector upper(101010, INF); -int bellmanFord() { - upper[1] = 0; - int update = 1; - for (int i = 0; i <= n; i++) { - update = 0; - for (int v = 1; v <= n; v++) { - if (upper[v] == INF) continue; - for (auto &i : adj[v]) { - if (upper[i.sc] > upper[v] + i.fr) { - upper[i.sc] = upper[v] + i.fr; - update = 1; - } - } - } - if (!update) break; - } - return !update; // Returns false <=> The graph has a negative cycle. -} - -// 3. Floyd-Warshall Algorithm -// INPUT: Given a directed graph with weighted(possibly negative) edges and no negative cycles. -// OUTPUT: Outputs the shortest distance from all vertices to all vertices. -// TIME COMPLEXITY: O(V^3) -int n, m; -ll adj[1010][1010]; -void floyd() { - for (int i = 0; i < 1010; i++) { - for (int j = 0; j < 1010; j++) { - adj[i][j] = (ll)1e18; - } - } - for (int i = 1; i <= n; i++) adj[i][i] = 0; - for (int k = 1; k <= n; k++) { - for (int u = 1; u <= n; u++) { - for (int v = 1; v <= n; v++) { - adj[u][v] = min(adj[u][v], adj[u][k] + adj[k][v]); - } - } - } -} \ No newline at end of file diff --git a/src/2-graph/euler_circuit.cpp b/src/2-graph/euler_circuit.cpp index b11757a..39287d5 100644 --- a/src/2-graph/euler_circuit.cpp +++ b/src/2-graph/euler_circuit.cpp @@ -1,54 +1,66 @@ #include "../common/common.hpp" -// Hierholzer's Algorithm -// INPUT: Given a undirected graph. -// OUTPUT: Print the path of the Euler circuit of the graph. -// Euler Path is a path in a finite graph that visits every edge exactly once. -// Similarly, an Euler Circuit is an Euler Path that starts and ends on the same vertex. -// TIME COMPLEXITY: O(VE) -const int MAXV = 1010; -int n, adj[MAXV][MAXV], nxt[MAXV]; -vector eulerCircult; -void input() { - cin >> n; - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= n; j++) { - cin >> adj[i][j]; - } +// what: Euler circuit via Hierholzer (undirected multigraph, matrix). +// time: O(n^2+m); memory: O(n^2) +// constraint: 1-indexed; all nonzero-degree nodes connected. +// usage: ecir g; g.init(n); g.add(u,v); if (g.can()) auto path=g.run(1); +struct ecir { + int n; + vector> adj; + vector nxt, path; + + void init(int n_) { + n = n_; + adj.assign(n + 1, vector(n + 1)); + nxt.assign(n + 1, 1); + path.clear(); + } + void add(int u, int v, int c = 1) { + if (u == v) adj[u][u] += 2 * c; + else adj[u][v] += c, adj[v][u] += c; } -} -int doesEulerCircuitExist() { - // If the degree of all nodes in the graph is even, then an euler circuit exists. - // Otherwise, the euler circuit does not exist. - // We can do similar way to determine the existence of euler path. - // If only two vertices have odd degree, than an eular path exists. Otherwise, the euler path does not exist. - for (int i = 1; i <= n; i++) { - int deg = 0; - for (int j = 1; j <= n; j++) { - deg += adj[i][j]; + bool can() { + vector deg(n + 1); + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) deg[i] += adj[i][j]; + for (int i = 1; i <= n; i++) + if (deg[i] & 1) return 0; + int s = 0; + for (int i = 1; i <= n; i++) + if (deg[i]) { + s = i; + break; + } + if (!s) return 1; + vector vis(n + 1); + queue q; + q.push(s); + vis[s] = 1; + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (int i = 1; i <= n; i++) + if (adj[v][i] && !vis[i]) vis[i] = 1, q.push(i); } - if (deg & 1) return 0; + for (int i = 1; i <= n; i++) + if (deg[i] && !vis[i]) return 0; + return 1; } - return 1; -} -void dfs(int now) { - for (int &x = nxt[now]; x <= n; x++) { - while (x <= n && adj[now][x]) { - adj[now][x]--; - adj[x][now]--; - dfs(x); + void dfs(int v) { + for (int &i = nxt[v]; i <= n; i++) { + while (i <= n && adj[v][i]) { + adj[v][i]--; + adj[i][v]--; + dfs(i); + } } + path.push_back(v); } - eulerCircult.push_back(now); -} -int main() { - input(); - if (!doesEulerCircuitExist()) { - cout << -1; - return 0; + vector run(int s = 1) { + for (int i = 1; i <= n; i++) nxt[i] = 1; + path.clear(); + dfs(s); + reverse(all(path)); + return path; } - for (int i = 1; i <= n; i++) nxt[i] = 1; - dfs(1); - for (auto i : eulerCircult) - cout << i << ' '; -} \ No newline at end of file +}; diff --git a/src/2-graph/kth_shortest_path.cpp b/src/2-graph/kth_shortest_path.cpp index dd99f4c..c165538 100644 --- a/src/2-graph/kth_shortest_path.cpp +++ b/src/2-graph/kth_shortest_path.cpp @@ -1,111 +1,103 @@ #include "../common/common.hpp" -// call init(|V|) first, add_edge(u, v, x), then kth_walk(s, e, K) -// O(|E|log|E|+klogk) in total -// if there is negative edge, O((SPFA time) + |E|log|E| + klogk) -// multi-edges, loops are allowed. -// ALERT: max answer = K|E||X| could be too large. -const int MAXN = 303030, HSIZE = 20202020; // HSIZE >= 2|E|log|E| -const ll INF = 1e18; // INF >= |E||X| (not need to be larger than K|E||X|) -int n; -vector> gph[MAXN], rgph[MAXN]; -struct Lheap { - struct Node { - pll x; - int l, r, s; - Node(void) : x({0, 0}), l(0), r(0), s(0) {} - Node(pll x) : x(x), l(0), r(0), s(1) {} - } h[HSIZE]; - int cnt = 1; - int mk(pll x) { - h[cnt] = Node(x); - return cnt++; - } - void norm(int x) { - if (h[h[x].l].s < h[h[x].r].s) swap(h[x].l, h[x].r); - h[x].s = h[h[x].r].s + 1; - } - int mrge(int x, int y) { - if (!x || !y) return x ^ y; - if (h[x].x > h[y].x) swap(x, y); - int ret = mk(h[x].x); - h[ret].l = h[x].l; - h[ret].r = mrge(h[x].r, y); - norm(ret); - return ret; +// what: k-th shortest walk (Eppstein-style, non-negative weights). +// time: O((n+m)log m + klog k); memory: O(n+m+heap) +// constraint: 1-indexed; w >= 0; n <= MAXN-1; recursion depth O(log m). +// usage: ksp g; g.init(n); g.add(u,v,w); auto v=g.run(s,e,k); +struct ksp { + static const int MAXN = 303030; + static const ll INF = (ll)1e18; + + int n; + vector g[MAXN], rg[MAXN]; + + struct lhp { + struct nd { + pll x; + int l, r, s; + }; + vector h; + void init() { h.assign(1, {{0, 0}, 0, 0, 0}); } + int mk(pll x) { + h.push_back({x, 0, 0, 1}); + return sz(h) - 1; + } + void norm(int x) { + if (h[h[x].l].s < h[h[x].r].s) swap(h[x].l, h[x].r); + h[x].s = h[h[x].r].s + 1; + } + int mrg(int x, int y) { + if (!x || !y) return x ^ y; + if (h[x].x > h[y].x) swap(x, y); + int ret = mk(h[x].x); + h[ret].l = h[x].l; + h[ret].r = mrg(h[x].r, y); + norm(ret); + return ret; + } + } hp; + + void init(int n_) { + n = n_; + for (int i = 1; i <= n; i++) g[i].clear(), rg[i].clear(); + hp.init(); } -} hp; -void init(int _n) { - n = _n; - for (int i = 1; i <= n; ++i) gph[i].clear(); - for (int i = 1; i <= n; ++i) rgph[i].clear(); - hp.cnt = 1; -} -void add_edge(int u, int v, ll w) { - gph[u].push_back({w, v}); - rgph[v].push_back({w, u}); -} -// return less than K elements if there is no such walk -vector kth_walk(int s, int e, int K) { - vector nxt(n + 1); - vector top; - vector vst(n + 1); - vector dst(n + 1, INF); - dst[e] = 0; - typedef tuple tlii; - // if there is no negative edge - priority_queue, greater> Q; - Q.push({0, e, -1}); - while (!Q.empty()) { - auto [d, x, p] = Q.top(); - Q.pop(); - if (vst[x]) continue; - vst[x] = 1; - nxt[x] = p; - top.push_back(x); - for (auto [c, y] : rgph[x]) - if (!vst[y] && dst[y] > d + c) dst[y] = d + c, Q.push({d + c, y, x}); + void add(int u, int v, ll w) { + g[u].push_back({w, v}); + rg[v].push_back({w, u}); } - // if there is negative edge - // nxt[e] = -1; - // for(int t = 0; t < n; ++t) { - // for(int x = 0; x < n; ++x) for(auto [c, y] : rgph[x]) if(dst[y] > dst[x] + c) - // dst[y] = dst[x] + c, nxt[y] = x; - // } - // // OR use SPFA - // vector ls[n]; - // for(int i = 0; i < n; ++i) if(nxt[i] != -1) ls[nxt[i]].push_back(i); - // queue Q; Q.push(e); - // while(Q.size()) { - // int x = Q.front(); Q.pop(); - // top.push_back(x); - // for(auto y : ls[x]) Q.push(y); - // } - if (dst[s] >= INF) return vector(); - vector chc(n + 1); - vector rt(n + 1); - for (auto x : top) - if (dst[x] < INF) { - if (nxt[x] != -1) rt[x] = rt[nxt[x]]; - for (auto [c, y] : gph[x]) - if (dst[y] < INF) { - if (!chc[x] && y == nxt[x] && dst[x] == c + dst[y]) { - chc[x] = 1; + + vector run(int s, int e, int k) { + vector nxt(n + 1, -1), ord, vis(n + 1); + vector dist(n + 1, INF); + + // goal: shortest path tree from e on reversed graph. + dist[e] = 0; + using tli = tuple; + priority_queue, greater> pq; + pq.push({0, e, -1}); + while (!pq.empty()) { + auto [d, x, p] = pq.top(); + pq.pop(); + if (vis[x]) continue; + vis[x] = 1; + nxt[x] = p; + ord.push_back(x); + for (auto [c, y] : rg[x]) { + if (!vis[y] && dist[y] > d + c) { + dist[y] = d + c; + pq.push({dist[y], y, x}); + } + } + } + if (dist[s] >= INF) return {}; + + vector rt(n + 1), chk(n + 1); + for (int x : ord) + if (dist[x] < INF) { + if (nxt[x] != -1) rt[x] = rt[nxt[x]]; + for (auto [c, y] : g[x]) { + if (dist[y] >= INF) continue; + if (!chk[x] && y == nxt[x] && dist[x] == c + dist[y]) { + chk[x] = 1; continue; } - rt[x] = hp.mrge(rt[x], hp.mk({c + dst[y] - dst[x], y})); + rt[x] = hp.mrg(rt[x], hp.mk({c + dist[y] - dist[x], y})); } + } + + vector ans = {dist[s]}; + priority_queue, greater> pq2; + if (rt[s]) pq2.push({hp.h[rt[s]].x.fr, rt[s]}); + while (sz(ans) < k && !pq2.empty()) { + auto [d, x] = pq2.top(); + pq2.pop(); + ans.push_back(dist[s] + d); + int y = hp.h[x].x.sc; + if (rt[y]) pq2.push({d + hp.h[rt[y]].x.fr, rt[y]}); + if (hp.h[x].l) pq2.push({d - hp.h[x].x.fr + hp.h[hp.h[x].l].x.fr, hp.h[x].l}); + if (hp.h[x].r) pq2.push({d - hp.h[x].x.fr + hp.h[hp.h[x].r].x.fr, hp.h[x].r}); } - vector ret({dst[s]}); - priority_queue, greater> PQ; - if (rt[s]) PQ.push({hp.h[rt[s]].x.fr, rt[s]}); - while (sz(ret) < K && !PQ.empty()) { - auto [d, x] = PQ.top(); - PQ.pop(); - ret.push_back(dst[s] + d); - if (rt[hp.h[x].x.sc]) PQ.push({d + hp.h[rt[hp.h[x].x.sc]].x.fr, rt[hp.h[x].x.sc]}); - if (hp.h[x].l) PQ.push({d - hp.h[x].x.fr + hp.h[hp.h[x].l].x.fr, hp.h[x].l}); - if (hp.h[x].r) PQ.push({d - hp.h[x].x.fr + hp.h[hp.h[x].r].x.fr, hp.h[x].r}); + return ans; } - return ret; -} \ No newline at end of file +}; diff --git a/src/2-graph/offline_dynamic_connectivity.cpp b/src/2-graph/offline_dynamic_connectivity.cpp index 0974c9a..80f689c 100644 --- a/src/2-graph/offline_dynamic_connectivity.cpp +++ b/src/2-graph/offline_dynamic_connectivity.cpp @@ -1,86 +1,105 @@ #include "../common/common.hpp" -int flag; -struct Seg { - vector> t; - void build(int n) { - for (flag = 1; flag < n; flag <<= 1); - t.resize(flag << 1); - } - void modify(int l, int r, pii val, int n = 1, int nl = 1, int nr = flag) { - if (r < nl || nr < l) return; - if (l <= nl && nr <= r) { - t[n].push_back(val); - return; +// what: offline dynamic connectivity with segment tree + rollback dsu. +// time: O((n+q)log q); memory: O(n+q) +// constraint: 1-indexed time; ops consistent; no parallel active edge. +// usage: odc g; g.init(n,q); g.add_op(i,op,u,v); g.build(); g.run(); +struct odc { + struct seg { + int flg; + vector> t; + void init(int n) { + for (flg = 1; flg < n; flg <<= 1) {} + t.assign(flg << 1, {}); } - int mid = (nl + nr) >> 1; - modify(l, r, val, n << 1, nl, mid); - modify(l, r, val, n << 1 | 1, mid + 1, nr); - } -} seg; -struct UF { - vector par, siz, stk; - void build(int n) { - par.resize(n + 1); - iota(all(par), 0); - siz.resize(n + 1, 1); - } - int find(int x) { - if (par[x] == x) return x; - return find(par[x]); - } - void merge(int u, int v) { - u = find(u), v = find(v); - if (siz[u] < siz[v]) swap(u, v); - par[v] = u; - siz[u] += siz[v]; - stk.push_back(v); - } - void restore() { - assert(!stk.empty()); - int v = stk.back(); - stk.pop_back(); - siz[par[v]] -= siz[v]; - par[v] = v; + void add(int l, int r, pii e, int n = 1, int nl = 1, int nr = -1) { + if (nr == -1) nr = flg; + if (r < nl || nr < l) return; + if (l <= nl && nr <= r) { + t[n].push_back(e); + return; + } + int mid = (nl + nr) >> 1; + add(l, r, e, n << 1, nl, mid); + add(l, r, e, n << 1 | 1, mid + 1, nr); + } + } sg; + + struct dsu { + vector par, siz, st; + void init(int n) { + par.resize(n + 1); + iota(all(par), 0); + siz.assign(n + 1, 1); + st.clear(); + } + int find(int x) { + while (par[x] != x) x = par[x]; + return x; + } + bool join(int a, int b) { + a = find(a), b = find(b); + if (a == b) return 0; + if (siz[a] < siz[b]) swap(a, b); + par[b] = a; + siz[a] += siz[b]; + st.push_back(b); + return 1; + } + void undo() { + int b = st.back(); + st.pop_back(); + int a = par[b]; + siz[a] -= siz[b]; + par[b] = b; + } + } uf; + + int n, q; + map mp; + vector qry; + vector ans; + + void init(int n_, int q_) { + n = n_; + q = q_; + mp.clear(); + sg.init(q); + uf.init(n); + qry.assign(q + 1, {0, 0}); + ans.assign(q + 1, 0); } -} uf; -int n, m; -map mp; -pii qu[101010]; -int ans[101010]; -void input() { - cin >> n >> m; - uf.build(n); - seg.build(m); - for (int i = 1; i <= m; i++) { - int op, u, v; - cin >> op >> u >> v; + void add_op(int i, int op, int u, int v) { if (u > v) swap(u, v); - - if (op == 1) mp[{u, v}] = i; - if (op == 2) seg.modify(mp[{u, v}], i - 1, {u, v}), mp.erase({u, v}); - if (op == 3) qu[i] = {u, v}; + if (op == 1) { + mp[{u, v}] = i; + } else if (op == 2) { + auto it = mp.find({u, v}); + if (it == mp.end()) return; + sg.add(it->second, i - 1, {u, v}); + mp.erase(it); + } else if (op == 3) { + qry[i] = {u, v}; + } + } + void build() { + for (auto &[e, l] : mp) sg.add(l, q, e); } - for (auto &[val, l] : mp) seg.modify(l, m, val); -} -void odc(int n = 1, int nl = 1, int nr = flag) { - int cnt = 0; - for (auto [u, v] : seg.t[n]) - if (uf.find(u) != uf.find(v)) uf.merge(u, v), cnt++; - if (nl == nr) { - if (nl <= m && qu[nl].fr) - ans[nl] = (uf.find(qu[nl].fr) == uf.find(qu[nl].sc)); - while (cnt--) uf.restore(); - return; + void dfs(int n = 1, int nl = 1, int nr = -1) { + if (nr == -1) nr = sg.flg; + int cnt = 0; + for (auto [u, v] : sg.t[n]) + if (uf.join(u, v)) cnt++; + if (nl == nr) { + if (nl <= q && qry[nl].fr) + ans[nl] = (uf.find(qry[nl].fr) == uf.find(qry[nl].sc)); + while (cnt--) uf.undo(); + return; + } + int mid = (nl + nr) >> 1; + dfs(n << 1, nl, mid); + dfs(n << 1 | 1, mid + 1, nr); + while (cnt--) uf.undo(); } - int mid = (nl + nr) >> 1; - odc(n << 1, nl, mid); - odc(n << 1 | 1, mid + 1, nr); - while (cnt--) uf.restore(); -} -int main() { - input(); - odc(); - for (int i = 1; i <= m; i++) - if (qu[i].fr) cout << ans[i] << '\n'; -} \ No newline at end of file + void run() { dfs(); } +}; diff --git a/src/2-graph/scc_2_sat.cpp b/src/2-graph/scc_2_sat.cpp index 180e71c..7e14332 100644 --- a/src/2-graph/scc_2_sat.cpp +++ b/src/2-graph/scc_2_sat.cpp @@ -1,221 +1,163 @@ #include "../common/common.hpp" -// 1. SCC (Kosaraju's Algorithm) -// INPUT: Given a directed graph. -// OUTPUT: Decompose this graph into SCCs and print them in lexicographical order. -// TIME COMPLEXITY: O(V + E) -const int MAXV = 10101; -int n, m; -vector adj[MAXV], radj[MAXV]; -int in[MAXV], out[MAXV], num, p[2 * MAXV]; -int vi[MAXV], cnt; -vector> scc; -void input() { - cin >> n >> m; - for (int i = 0; i < m; i++) { - int u, v; - cin >> u >> v; - adj[u].push_back(v); - radj[v].push_back(u); - } -} -void dfs(int v) { - in[v] = ++num; - for (auto &i : radj[v]) { - if (!in[i]) dfs(i); - } - out[v] = ++num; - p[num] = v; -} -void flood(int v) { - scc[cnt].push_back(v); - vi[v] = cnt; - for (auto &i : adj[v]) { - if (!vi[i]) flood(i); - } -} -void kosaraju() { - for (int v = 1; v <= n; v++) { - if (!in[v]) dfs(v); - } - for (int v = 2 * n; v >= 1; v--) { - if (!p[v]) continue; - if (vi[p[v]]) continue; - cnt++; - scc.resize(cnt + 1); - flood(p[v]); - } -} -void print() { - for (auto &i : scc) - sort(i.begin(), i.end()); - sort(scc.begin(), scc.end()); - cout << sz(scc) - 1 << '\n'; - for (int i = 1; i < sz(scc); i++) { - auto &arr = scc[i]; - for (auto &j : arr) cout << j << ' '; - cout << -1 << '\n'; - } -} -int main() { - input(); - kosaraju(); - print(); -} +// what: SCC via Kosaraju. +// time: O(n+m); memory: O(n+m) +// constraint: directed; 1-indexed; recursion depth O(n). +// usage: scc_ko s; s.init(n); s.add(u,v); int c=s.run(); +struct scc_ko { + int n; + vector> g, rg, sccs; + vector vis, comp, ord; -// 2. SCC (Tarjan's strongly connected components algorithm) -// INPUT: Given a directed graph. -// OUTPUT: Decompose this graph into SCCs and print them in lexicographical order. -// TIME COMPLEXITY: O(V + E) -const int MAXV = 101010; -int n, m, label[MAXV], labelCnt; -int SCCnum[MAXV], SCCcnt, finished[MAXV]; -vector adj[MAXV]; -stack stk; -vector> SCC; -void input() { - cin >> n >> m; - for (int i = 0; i < m; i++) { - int u, v; - cin >> u >> v; - adj[u].push_back(v); - } -} -int dfs(int v) { - label[v] = labelCnt++; - stk.push(v); - int ret = label[v]; - for (int next : adj[v]) { - // Unvisited node. - if (label[next] == -1) ret = min(ret, dfs(next)); - // Visited but not yet classified as SCC. In other words, edge { v, next } is back edge. - else if (!finished[next]) ret = min(ret, label[next]); + void init(int n_) { + n = n_; + g.assign(n + 1, {}); + rg.assign(n + 1, {}); + sccs.clear(); + vis.assign(n + 1, 0); + comp.assign(n + 1, -1); + ord.clear(); + } + void add(int u, int v) { + g[u].push_back(v); + rg[v].push_back(u); + } + void dfs1(int v) { + vis[v] = 1; + for (int to : rg[v]) + if (!vis[to]) dfs1(to); + ord.push_back(v); + } + void dfs2(int v, int id) { + comp[v] = id; + sccs[id].push_back(v); + for (int to : g[v]) + if (comp[to] == -1) dfs2(to, id); + } + int run() { + for (int v = 1; v <= n; v++) + if (!vis[v]) dfs1(v); + reverse(all(ord)); + for (int v : ord) { + if (comp[v] != -1) continue; + sccs.push_back({}); + dfs2(v, sz(sccs) - 1); + } + return sz(sccs); } - // If there is no edge to the ancestor node among itself and its descendants, find scc. - if (ret == label[v]) { - vector vSCC; +}; + +// what: SCC via Tarjan. +// time: O(n+m); memory: O(n+m) +// constraint: directed; 1-indexed; recursion depth O(n). +// usage: scc_ta s; s.init(n); s.add(u,v); int c=s.run(); +struct scc_ta { + int n, tim; + vector> g, sccs; + vector dfn, low, comp, st, ins; + + void init(int n_) { + n = n_; + tim = 0; + g.assign(n + 1, {}); + sccs.clear(); + dfn.assign(n + 1, -1); + low.assign(n + 1, 0); + comp.assign(n + 1, -1); + ins.assign(n + 1, 0); + st.clear(); + } + void add(int u, int v) { g[u].push_back(v); } + void dfs(int v) { + dfn[v] = low[v] = ++tim; + st.push_back(v); + ins[v] = 1; + for (int to : g[v]) { + if (dfn[to] == -1) { + dfs(to); + low[v] = min(low[v], low[to]); + } else if (ins[to]) { + low[v] = min(low[v], dfn[to]); + } + } + if (low[v] != dfn[v]) return; + sccs.push_back({}); + int id = sz(sccs) - 1; while (1) { - int t = stk.top(); - stk.pop(); - vSCC.push_back(t); - SCCnum[t] = SCCcnt; - finished[t] = 1; - if (t == v) break; + int x = st.back(); + st.pop_back(); + ins[x] = 0; + comp[x] = id; + sccs[id].push_back(x); + if (x == v) break; } - SCC.push_back(vSCC); - SCCcnt++; } - return ret; -} -void getSCC() { - memset(label, -1, sizeof(label)); - for (int v = 1; v <= n; v++) - if (label[v] == -1) dfs(v); -} -void print() { - for (auto &i : SCC) - sort(i.begin(), i.end()); - sort(SCC.begin(), SCC.end()); - cout << sz(SCC) << '\n'; - for (int i = 0; i < sz(SCC); i++) { - auto &arr = SCC[i]; - for (auto &j : arr) cout << j << ' '; - cout << -1 << '\n'; + int run() { + for (int v = 1; v <= n; v++) + if (dfn[v] == -1) dfs(v); + return sz(sccs); } -} -int main() { - input(); - getSCC(); - print(); -} +}; -// 3. 2-SAT -// INPUT: A 2-CNF is given. 2-CNF is a boolean expression in the form (x ∨ y) ∧ (¬ y ∨ z) ∧ (x ∨ ¬ z) ∧ (z ∨ y). -// OUTPUT: Determine whether there exists a case where a given 2-CNF expression can be true. (2-Satisfiability Problem) -// TIME COMPLEXITY: O(n + m) = O(n) (m = 2n) +// what: 2-SAT via SCC (Tarjan). +// time: O(n+m); memory: O(n+m) +// constraint: vars are 1..n; literal x<0 means not x; recursion depth O(n). +// usage: two_sat s; s.init(n); s.add(a,b); bool ok=s.run(); // s.val +struct two_sat { + int n, tim, cid; + vector> g; + vector dfn, low, comp, st, ins, val; -// BOJ 11281 AC Code -// https://www.acmicpc.net/problem/11281 -const int MAXV = 20202; -int n, m; -int dfsn[MAXV], dCnt, sNum[MAXV], sCnt; -int finished[MAXV]; -vector adj[MAXV]; -stack stk; -pii p[MAXV]; -int ans[MAXV / 2]; -inline int inv(int x) { - // negative number -a indicates ¬a. - return (x > 0) ? 2 * (x - 1) : 2 * (-x - 1) + 1; -} -void twoCnf(int a, int b) { - // (a ∨ b) iff (¬a → b) iff (¬b → a) - adj[inv(-a)].push_back(inv(b)); - adj[inv(-b)].push_back(inv(a)); -} -void input() { - cin >> n >> m; - for (int i = 0; i < m; i++) { - int a, b; - cin >> a >> b; - twoCnf(a, b); - } -} -int dfs(int now) { - int ret = dfsn[now] = ++dCnt; - stk.push(now); - for (int next : adj[now]) { - if (dfsn[next] == -1) ret = min(ret, dfs(next)); - else if (!finished[next]) ret = min(ret, dfsn[next]); - } - if (ret >= dfsn[now]) { + void init(int n_) { + n = n_; + g.assign(2 * n, {}); + dfn.assign(2 * n, -1); + low.assign(2 * n, 0); + comp.assign(2 * n, -1); + ins.assign(2 * n, 0); + st.clear(); + val.assign(n + 1, 0); + tim = 0; + cid = 0; + } + int id(int x) { + // goal: x in [-n, n]\{0} -> node id. + return x > 0 ? 2 * (x - 1) : 2 * (-x - 1) + 1; + } + void add(int a, int b) { + // goal: (a v b) == (!a -> b) & (!b -> a) + g[id(-a)].push_back(id(b)); + g[id(-b)].push_back(id(a)); + } + void dfs(int v) { + dfn[v] = low[v] = ++tim; + st.push_back(v); + ins[v] = 1; + for (int to : g[v]) { + if (dfn[to] == -1) { + dfs(to); + low[v] = min(low[v], low[to]); + } else if (ins[to]) { + low[v] = min(low[v], dfn[to]); + } + } + if (low[v] != dfn[v]) return; while (1) { - int t = stk.top(); - stk.pop(); - sNum[t] = sCnt; - finished[t] = 1; - if (t == now) break; + int x = st.back(); + st.pop_back(); + ins[x] = 0; + comp[x] = cid; + if (x == v) break; } - sCnt++; - } - return ret; -} -int isSatisfiable() { - // determining satisfiability - int isS = 1; - for (int v = 0; v < 2 * n; v += 2) { - // if x and ¬x is in same scc, then the proposition is not satisfiable - if (sNum[v] == sNum[v + 1]) { - isS = 0; - break; + cid++; + } + bool run() { + for (int i = 0; i < 2 * n; i++) + if (dfn[i] == -1) dfs(i); + for (int i = 0; i < n; i++) { + if (comp[2 * i] == comp[2 * i + 1]) return 0; + val[i + 1] = comp[2 * i] < comp[2 * i + 1]; } + return 1; } - return isS; -} -void findValueOfEachVariable() { - // order of scc is the reverse of the topological sort - for (int v = 0; v < 2 * n; v++) { - p[v] = {sNum[v], v}; - } - sort(p, p + 2 * n); - // determining true/false of each variable - for (int i = 2 * n - 1; i >= 0; i--) { - int v = p[i].sc; - if (ans[v / 2 + 1] == -1) - ans[v / 2 + 1] = (v & 1) ? 1 : 0; - } - for (int v = 1; v <= n; v++) - cout << ans[v] << ' '; -} -int main() { - memset(dfsn, -1, sizeof(dfsn)); - memset(ans, -1, sizeof(ans)); - input(); - // finding scc - for (int v = 0; v < 2 * n; v++) - if (dfsn[v] == -1) dfs(v); - if (isSatisfiable()) { - cout << 1 << '\n'; - findValueOfEachVariable(); - } else cout << 0; -} \ No newline at end of file +}; diff --git a/src/2-graph/shortest_path.cpp b/src/2-graph/shortest_path.cpp new file mode 100644 index 0000000..28ccaf6 --- /dev/null +++ b/src/2-graph/shortest_path.cpp @@ -0,0 +1,96 @@ +#include "../common/common.hpp" + +// what: Dijkstra shortest path (non-negative weights). +// time: O((n+m)log n); memory: O(n+m) +// constraint: directed; 1-indexed; w >= 0. +// usage: dijk g; g.init(n); g.add(u,v,w); auto dist=g.run(s); +struct dijk { + static const ll INF = (1LL << 62); + int n; + vector> adj; + + void init(int n_) { + n = n_; + adj.assign(n + 1, {}); + } + void add(int u, int v, ll w) { adj[u].push_back({w, v}); } + vector run(int s) { + vector dist(n + 1, INF); + priority_queue, greater> pq; + dist[s] = 0; + pq.push({0, s}); + while (!pq.empty()) { + auto [d, v] = pq.top(); + pq.pop(); + if (d != dist[v]) continue; + for (auto [w, to] : adj[v]) { + if (dist[to] > d + w) { + dist[to] = d + w; + pq.push({dist[to], to}); + } + } + } + return dist; + } +}; + +// what: Bellman-Ford shortest path. +// time: O(nm); memory: O(n+m) +// constraint: directed; 1-indexed; detects negative cycle reachable from s. +// usage: bell g; g.init(n); g.add(u,v,w); bool ok=g.run(s, dist); +struct bell { + static const ll INF = (1LL << 62); + int n; + vector> ed; + + void init(int n_) { + n = n_; + ed.clear(); + } + void add(int u, int v, ll w) { ed.push_back({u, v, w}); } + bool run(int s, vector &dist) { + dist.assign(n + 1, INF); + dist[s] = 0; + bool upd = 0; + for (int i = 1; i <= n; i++) { + upd = 0; + for (auto [u, v, w] : ed) { + if (dist[u] == INF) continue; + if (dist[v] > dist[u] + w) { + dist[v] = dist[u] + w; + upd = 1; + } + } + if (!upd) break; + } + return !upd; + } +}; + +// what: Floyd-Warshall all-pairs shortest paths. +// time: O(n^3); memory: O(n^2) +// constraint: directed; 1-indexed; watch overflow on INF. +// usage: floyd g; g.init(n); g.add(u,v,w); g.run(); auto &d=g.d; +struct floyd { + static const ll INF = (1LL << 62); + int n; + vector> d; + + void init(int n_) { + n = n_; + d.assign(n + 1, vector(n + 1, INF)); + for (int i = 1; i <= n; i++) d[i][i] = 0; + } + void add(int u, int v, ll w) { d[u][v] = min(d[u][v], w); } + void run() { + for (int k = 1; k <= n; k++) { + for (int i = 1; i <= n; i++) { + if (d[i][k] == INF) continue; + for (int j = 1; j <= n; j++) { + if (d[k][j] == INF) continue; + d[i][j] = min(d[i][j], d[i][k] + d[k][j]); + } + } + } + } +}; diff --git a/src/2-graph/tree_cd.cpp b/src/3-tree/tree_cd.cpp similarity index 100% rename from src/2-graph/tree_cd.cpp rename to src/3-tree/tree_cd.cpp diff --git a/src/2-graph/tree_composition.cpp b/src/3-tree/tree_composition.cpp similarity index 100% rename from src/2-graph/tree_composition.cpp rename to src/3-tree/tree_composition.cpp diff --git a/src/2-graph/tree_exchange_argument.cpp b/src/3-tree/tree_exchange_argument.cpp similarity index 100% rename from src/2-graph/tree_exchange_argument.cpp rename to src/3-tree/tree_exchange_argument.cpp diff --git a/src/2-graph/tree_hld.cpp b/src/3-tree/tree_hld.cpp similarity index 100% rename from src/2-graph/tree_hld.cpp rename to src/3-tree/tree_hld.cpp diff --git a/src/2-graph/tree_lca_sparse_table.cpp b/src/3-tree/tree_lca_sparse_table.cpp similarity index 100% rename from src/2-graph/tree_lca_sparse_table.cpp rename to src/3-tree/tree_lca_sparse_table.cpp diff --git a/src/3-optimizations/flow.cpp b/src/4-optimizations/flow.cpp similarity index 100% rename from src/3-optimizations/flow.cpp rename to src/4-optimizations/flow.cpp diff --git a/src/3-optimizations/hungarian.cpp b/src/4-optimizations/hungarian.cpp similarity index 100% rename from src/3-optimizations/hungarian.cpp rename to src/4-optimizations/hungarian.cpp diff --git a/src/4-string/aho_corasick.cpp b/src/5-string/aho_corasick.cpp similarity index 100% rename from src/4-string/aho_corasick.cpp rename to src/5-string/aho_corasick.cpp diff --git a/src/4-string/kmp_algorithm.cpp b/src/5-string/kmp_algorithm.cpp similarity index 100% rename from src/4-string/kmp_algorithm.cpp rename to src/5-string/kmp_algorithm.cpp diff --git a/src/4-string/manachers_algorithm.cpp b/src/5-string/manachers_algorithm.cpp similarity index 100% rename from src/4-string/manachers_algorithm.cpp rename to src/5-string/manachers_algorithm.cpp diff --git a/src/4-string/rabin_karp_algorithm.cpp b/src/5-string/rabin_karp_algorithm.cpp similarity index 100% rename from src/4-string/rabin_karp_algorithm.cpp rename to src/5-string/rabin_karp_algorithm.cpp diff --git a/src/4-string/suffix_array.cpp b/src/5-string/suffix_array.cpp similarity index 100% rename from src/4-string/suffix_array.cpp rename to src/5-string/suffix_array.cpp diff --git a/src/4-string/trie.cpp b/src/5-string/trie.cpp similarity index 100% rename from src/4-string/trie.cpp rename to src/5-string/trie.cpp diff --git a/src/4-string/z_algorithm.cpp b/src/5-string/z_algorithm.cpp similarity index 100% rename from src/4-string/z_algorithm.cpp rename to src/5-string/z_algorithm.cpp diff --git a/src/5-geometry/bulldozer_trick.cpp b/src/6-geometry/bulldozer_trick.cpp similarity index 100% rename from src/5-geometry/bulldozer_trick.cpp rename to src/6-geometry/bulldozer_trick.cpp diff --git a/src/5-geometry/ccw_algorithm.cpp b/src/6-geometry/ccw_algorithm.cpp similarity index 100% rename from src/5-geometry/ccw_algorithm.cpp rename to src/6-geometry/ccw_algorithm.cpp diff --git a/src/5-geometry/convex_hull.cpp b/src/6-geometry/convex_hull.cpp similarity index 100% rename from src/5-geometry/convex_hull.cpp rename to src/6-geometry/convex_hull.cpp diff --git a/src/5-geometry/half_plane_intersection.cpp b/src/6-geometry/half_plane_intersection.cpp similarity index 100% rename from src/5-geometry/half_plane_intersection.cpp rename to src/6-geometry/half_plane_intersection.cpp diff --git a/src/5-geometry/minimum_enclosing_circle.cpp b/src/6-geometry/minimum_enclosing_circle.cpp similarity index 100% rename from src/5-geometry/minimum_enclosing_circle.cpp rename to src/6-geometry/minimum_enclosing_circle.cpp diff --git a/src/5-geometry/ray_casting.cpp b/src/6-geometry/ray_casting.cpp similarity index 100% rename from src/5-geometry/ray_casting.cpp rename to src/6-geometry/ray_casting.cpp diff --git a/src/5-geometry/rotating_callipers.cpp b/src/6-geometry/rotating_callipers.cpp similarity index 100% rename from src/5-geometry/rotating_callipers.cpp rename to src/6-geometry/rotating_callipers.cpp diff --git a/src/5-geometry/sort_by_angular.cpp b/src/6-geometry/sort_by_angular.cpp similarity index 100% rename from src/5-geometry/sort_by_angular.cpp rename to src/6-geometry/sort_by_angular.cpp diff --git a/src/6-math/basic_sqrt_time_algorithms.cpp b/src/7-math/basic_sqrt_time_algorithms.cpp similarity index 100% rename from src/6-math/basic_sqrt_time_algorithms.cpp rename to src/7-math/basic_sqrt_time_algorithms.cpp diff --git a/src/6-math/binomial_coefficient.cpp b/src/7-math/binomial_coefficient.cpp similarity index 100% rename from src/6-math/binomial_coefficient.cpp rename to src/7-math/binomial_coefficient.cpp diff --git a/src/6-math/catalan_number_derangement_number.cpp b/src/7-math/catalan_number_derangement_number.cpp similarity index 100% rename from src/6-math/catalan_number_derangement_number.cpp rename to src/7-math/catalan_number_derangement_number.cpp diff --git a/src/6-math/chinese_remainder_theorem.cpp b/src/7-math/chinese_remainder_theorem.cpp similarity index 100% rename from src/6-math/chinese_remainder_theorem.cpp rename to src/7-math/chinese_remainder_theorem.cpp diff --git a/src/6-math/euclidean_algorithms.cpp b/src/7-math/euclidean_algorithms.cpp similarity index 100% rename from src/6-math/euclidean_algorithms.cpp rename to src/7-math/euclidean_algorithms.cpp diff --git a/src/6-math/eulers_phi_function.cpp b/src/7-math/eulers_phi_function.cpp similarity index 100% rename from src/6-math/eulers_phi_function.cpp rename to src/7-math/eulers_phi_function.cpp diff --git a/src/6-math/fft.cpp b/src/7-math/fft.cpp similarity index 100% rename from src/6-math/fft.cpp rename to src/7-math/fft.cpp diff --git a/src/6-math/gauss_jordan_elimination.cpp b/src/7-math/gauss_jordan_elimination.cpp similarity index 100% rename from src/6-math/gauss_jordan_elimination.cpp rename to src/7-math/gauss_jordan_elimination.cpp diff --git a/src/6-math/matrix.cpp b/src/7-math/matrix.cpp similarity index 100% rename from src/6-math/matrix.cpp rename to src/7-math/matrix.cpp diff --git a/src/6-math/miller_rabin_pollard_rho.cpp b/src/7-math/miller_rabin_pollard_rho.cpp similarity index 100% rename from src/6-math/miller_rabin_pollard_rho.cpp rename to src/7-math/miller_rabin_pollard_rho.cpp diff --git a/src/6-math/mobius.cpp b/src/7-math/mobius.cpp similarity index 100% rename from src/6-math/mobius.cpp rename to src/7-math/mobius.cpp diff --git a/src/6-math/sieve.cpp b/src/7-math/sieve.cpp similarity index 100% rename from src/6-math/sieve.cpp rename to src/7-math/sieve.cpp diff --git a/src/7-misc/dp_opt.cpp b/src/8-misc/dp_opt.cpp similarity index 100% rename from src/7-misc/dp_opt.cpp rename to src/8-misc/dp_opt.cpp diff --git a/src/7-misc/fraction_data_type.cpp b/src/8-misc/fraction_data_type.cpp similarity index 100% rename from src/7-misc/fraction_data_type.cpp rename to src/8-misc/fraction_data_type.cpp diff --git a/src/7-misc/kitamasa.cpp b/src/8-misc/kitamasa.cpp similarity index 100% rename from src/7-misc/kitamasa.cpp rename to src/8-misc/kitamasa.cpp diff --git a/src/7-misc/lis_in_o_nlogn.cpp b/src/8-misc/lis_in_o_nlogn.cpp similarity index 100% rename from src/7-misc/lis_in_o_nlogn.cpp rename to src/8-misc/lis_in_o_nlogn.cpp diff --git a/src/7-misc/random.cpp b/src/8-misc/random.cpp similarity index 100% rename from src/7-misc/random.cpp rename to src/8-misc/random.cpp diff --git a/src/7-misc/rotation_matrix_manhattan_distance_chebyshev_distance.txt b/src/8-misc/rotation_matrix_manhattan_distance_chebyshev_distance.txt similarity index 100% rename from src/7-misc/rotation_matrix_manhattan_distance_chebyshev_distance.txt rename to src/8-misc/rotation_matrix_manhattan_distance_chebyshev_distance.txt diff --git a/src/7-misc/simd.cpp b/src/8-misc/simd.cpp similarity index 100% rename from src/7-misc/simd.cpp rename to src/8-misc/simd.cpp diff --git a/src/7-misc/sqrt_decomposition_mos_algorithm.cpp b/src/8-misc/sqrt_decomposition_mos_algorithm.cpp similarity index 100% rename from src/7-misc/sqrt_decomposition_mos_algorithm.cpp rename to src/8-misc/sqrt_decomposition_mos_algorithm.cpp diff --git a/src/7-misc/stress_test.py b/src/8-misc/stress_test.py similarity index 100% rename from src/7-misc/stress_test.py rename to src/8-misc/stress_test.py diff --git a/src/7-misc/system_of_difference_constraints.cpp b/src/8-misc/system_of_difference_constraints.cpp similarity index 100% rename from src/7-misc/system_of_difference_constraints.cpp rename to src/8-misc/system_of_difference_constraints.cpp diff --git a/src/7-misc/ternary_search.cpp b/src/8-misc/ternary_search.cpp similarity index 100% rename from src/7-misc/ternary_search.cpp rename to src/8-misc/ternary_search.cpp diff --git a/tests/2-graph/test_bcc.cpp b/tests/2-graph/test_bcc.cpp new file mode 100644 index 0000000..98d25bf --- /dev/null +++ b/tests/2-graph/test_bcc.cpp @@ -0,0 +1,158 @@ +#include "../../src/2-graph/bcc.cpp" + +// what: tests for bcc. +// time: random + brute checks; memory: O(n+m) +// constraint: small n brute. +// usage: g++ -std=c++17 test_bcc.cpp && ./a.out + +mt19937_64 rng(1); +int rnd(int l, int r) { + uniform_int_distribution dis(l, r); + return dis(rng); +} + +int ccnt(int n, const vector &ed, int sv, int se) { + vector> adj(n + 1); + for (int i = 0; i < sz(ed); i++) { + if (i == se) continue; + int u = ed[i].fr, v = ed[i].sc; + if (u == sv || v == sv) continue; + adj[u].push_back(v); + adj[v].push_back(u); + } + vector vis(n + 1); + int cnt = 0; + for (int v = 1; v <= n; v++) { + if (v == sv || vis[v]) continue; + cnt++; + queue q; + q.push(v); + vis[v] = 1; + while (!q.empty()) { + int x = q.front(); + q.pop(); + for (int to : adj[x]) { + if (!vis[to]) vis[to] = 1, q.push(to); + } + } + } + return cnt; +} + +vector ap_na(int n, const vector &ed) { + int base = ccnt(n, ed, 0, -1); + vector ap; + for (int v = 1; v <= n; v++) + if (ccnt(n, ed, v, -1) > base) ap.push_back(v); + return ap; +} + +vector ae_na(int n, const vector &ed) { + int base = ccnt(n, ed, 0, -1); + vector ae; + for (int i = 0; i < sz(ed); i++) { + if (ccnt(n, ed, 0, i) > base) { + int u = ed[i].fr, v = ed[i].sc; + if (u > v) swap(u, v); + ae.push_back({u, v}); + } + } + sort(all(ae)); + ae.erase(unique(all(ae)), ae.end()); + return ae; +} + +void chk_bcc(int n, const vector &ed, bcc &g) { + map cnt, got; + for (auto [u, v] : ed) { + if (u > v) swap(u, v); + cnt[{u, v}]++; + } + for (auto &comp : g.bccs) { + for (auto [u, v] : comp) { + if (u > v) swap(u, v); + got[{u, v}]++; + } + } + assert(cnt == got); + + for (auto &comp : g.bccs) { + if (sz(comp) <= 1) continue; + vector>> adj(n + 1); + for (int i = 0; i < sz(comp); i++) { + int u = comp[i].fr, v = comp[i].sc; + adj[u].push_back({v, i}); + adj[v].push_back({u, i}); + } + for (int i = 0; i < sz(comp); i++) { + int u = comp[i].fr, v = comp[i].sc; + vector vis(n + 1); + queue q; + q.push(u); + vis[u] = 1; + while (!q.empty()) { + int x = q.front(); + q.pop(); + for (auto [to, id] : adj[x]) { + if (id == i || vis[to]) continue; + vis[to] = 1; + q.push(to); + } + } + assert(vis[v]); + } + } +} + +void t_fix() { + int n = 3; + vector ed = {{1, 2}, {2, 3}}; + bcc g; + g.init(n); + for (auto [u, v] : ed) g.add(u, v); + g.run(); + auto ap = g.ap; + auto ae = g.ae; + sort(all(ap)); + sort(all(ae)); + assert(ap == vector({2})); + assert(ae == vector({{1, 2}, {2, 3}})); + chk_bcc(n, ed, g); +} + +void t_rnd() { + for (int it = 0; it < 200; it++) { + int n = rnd(2, 7); + int m = rnd(0, 10); + vector ed; + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + if (u == v) { + i--; + continue; + } + ed.push_back({u, v}); + } + bcc g; + g.init(n); + for (auto [u, v] : ed) g.add(u, v); + g.run(); + auto ap = g.ap; + auto ae = g.ae; + sort(all(ap)); + ap.erase(unique(all(ap)), ap.end()); + sort(all(ae)); + ae.erase(unique(all(ae)), ae.end()); + auto ap2 = ap_na(n, ed); + auto ae2 = ae_na(n, ed); + assert(ap == ap2); + assert(ae == ae2); + chk_bcc(n, ed, g); + } +} + +int main() { + t_fix(); + t_rnd(); + return 0; +} diff --git a/tests/2-graph/test_euler_circuit.cpp b/tests/2-graph/test_euler_circuit.cpp new file mode 100644 index 0000000..db45b80 --- /dev/null +++ b/tests/2-graph/test_euler_circuit.cpp @@ -0,0 +1,141 @@ +#include "../../src/2-graph/euler_circuit.cpp" + +// what: tests for Euler circuit. +// time: random + edge cases; memory: O(n^2) +// constraint: small n brute. +// usage: g++ -std=c++17 test_euler_circuit.cpp && ./a.out + +mt19937_64 rng(2); +int rnd(int l, int r) { + uniform_int_distribution dis(l, r); + return dis(rng); +} + +int ecnt(int n, const vector> &cnt) { + int m = 0; + for (int i = 1; i <= n; i++) m += cnt[i][i]; + for (int i = 1; i <= n; i++) + for (int j = i + 1; j <= n; j++) m += cnt[i][j]; + return m; +} + +bool can_na(int n, const vector> &cnt) { + vector deg(n + 1); + for (int i = 1; i <= n; i++) { + deg[i] += 2 * cnt[i][i]; + for (int j = 1; j <= n; j++) + if (i != j) deg[i] += cnt[i][j]; + } + for (int i = 1; i <= n; i++) + if (deg[i] & 1) return 0; + int s = 0; + for (int i = 1; i <= n; i++) + if (deg[i]) { + s = i; + break; + } + if (!s) return 1; + vector vis(n + 1); + queue q; + q.push(s); + vis[s] = 1; + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (int i = 1; i <= n; i++) { + if (i == v || cnt[v][i] == 0 || vis[i]) continue; + vis[i] = 1; + q.push(i); + } + } + for (int i = 1; i <= n; i++) + if (deg[i] && !vis[i]) return 0; + return 1; +} + +void add_ed(ecir &g, vector> &cnt, int u, int v) { + g.add(u, v); + if (u == v) cnt[u][u]++; + else cnt[u][v]++, cnt[v][u]++; +} + +void chk_run(int n, const vector> &cnt, const vector &path) { + int m = ecnt(n, cnt); + if (m == 0) { + assert(sz(path) == 1); + return; + } + assert(path.front() == path.back()); + assert(sz(path) == m + 1); + auto rem = cnt; + for (int i = 0; i + 1 < sz(path); i++) { + int a = path[i], b = path[i + 1]; + if (a == b) { + assert(rem[a][a] > 0); + rem[a][a]--; + } else { + assert(rem[a][b] > 0); + rem[a][b]--; + rem[b][a]--; + } + } + assert(ecnt(n, rem) == 0); +} + +void t_fix() { + int n = 3; + ecir g; + g.init(n); + vector> cnt(n + 1, vector(n + 1)); + add_ed(g, cnt, 1, 2); + add_ed(g, cnt, 2, 3); + add_ed(g, cnt, 3, 1); + assert(g.can()); + auto path = g.run(1); + chk_run(n, cnt, path); + + g.init(n); + cnt.assign(n + 1, vector(n + 1)); + add_ed(g, cnt, 1, 2); + add_ed(g, cnt, 2, 3); + assert(!g.can()); +} + +void t_rnd() { + for (int it = 0; it < 200; it++) { + int n = rnd(1, 6); + int m = rnd(0, 8); + ecir g; + g.init(n); + vector> cnt(n + 1, vector(n + 1)); + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + add_ed(g, cnt, u, v); + } + bool ok1 = g.can(); + bool ok2 = can_na(n, cnt); + assert(ok1 == ok2); + if (ok1) { + int s = 1; + vector deg(n + 1); + for (int i = 1; i <= n; i++) { + deg[i] += 2 * cnt[i][i]; + for (int j = 1; j <= n; j++) + if (i != j) deg[i] += cnt[i][j]; + } + for (int i = 1; i <= n; i++) + if (deg[i]) { + s = i; + break; + } + auto path = g.run(s); + chk_run(n, cnt, path); + } + } +} + +int main() { + t_fix(); + t_rnd(); + return 0; +} diff --git a/tests/2-graph/test_kth_shortest_path.cpp b/tests/2-graph/test_kth_shortest_path.cpp new file mode 100644 index 0000000..110aec4 --- /dev/null +++ b/tests/2-graph/test_kth_shortest_path.cpp @@ -0,0 +1,69 @@ +#include "../../src/2-graph/kth_shortest_path.cpp" + +// what: tests for k-th shortest walk. +// time: random + brute; memory: O(n+m) +// constraint: small n, positive weights. +// usage: g++ -std=c++17 test_kth_shortest_path.cpp && ./a.out + +mt19937_64 rng(5); +int rnd(int l, int r) { + uniform_int_distribution dis(l, r); + return dis(rng); +} + +ksp ks; + +vector kth_na(int n, const vector> &g, int s, int e, int k) { + vector cnt(n + 1); + priority_queue, greater> pq; + pq.push({0, s}); + vector res; + while (!pq.empty() && sz(res) < k) { + auto [d, v] = pq.top(); + pq.pop(); + cnt[v]++; + if (cnt[v] > k) continue; + if (v == e) res.push_back(d); + if (cnt[v] <= k) + for (auto [w, to] : g[v]) pq.push({d + w, to}); + } + return res; +} + +void t_fix() { + int n = 3; + vector> g(n + 1); + g[1].push_back({1, 2}); + g[2].push_back({1, 3}); + ks.init(n); + ks.add(1, 2, 1); + ks.add(2, 3, 1); + auto a = kth_na(n, g, 1, 3, 3); + auto b = ks.run(1, 3, 3); + assert(a == b); +} + +void t_rnd() { + for (int it = 0; it < 200; it++) { + int n = rnd(2, 6); + int m = rnd(0, n * (n - 1)); + vector> g(n + 1); + ks.init(n); + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + ll w = rnd(1, 5); + g[u].push_back({w, v}); + ks.add(u, v, w); + } + int s = rnd(1, n), e = rnd(1, n), k = rnd(1, 10); + auto a = kth_na(n, g, s, e, k); + auto b = ks.run(s, e, k); + assert(a == b); + } +} + +int main() { + t_fix(); + t_rnd(); + return 0; +} diff --git a/tests/2-graph/test_offline_dynamic_connectivity.cpp b/tests/2-graph/test_offline_dynamic_connectivity.cpp new file mode 100644 index 0000000..a3dd3b7 --- /dev/null +++ b/tests/2-graph/test_offline_dynamic_connectivity.cpp @@ -0,0 +1,117 @@ +#include "../../src/2-graph/offline_dynamic_connectivity.cpp" + +// what: tests for offline dynamic connectivity. +// time: random + brute; memory: O(n+q) +// constraint: ops consistent; small n brute. +// usage: g++ -std=c++17 test_offline_dynamic_connectivity.cpp && ./a.out + +mt19937_64 rng(6); +int rnd(int l, int r) { + uniform_int_distribution dis(l, r); + return dis(rng); +} + +int conn(int n, const set &act, int s, int e) { + if (s == e) return 1; + vector> adj(n + 1); + for (auto [u, v] : act) { + adj[u].push_back(v); + adj[v].push_back(u); + } + vector vis(n + 1); + queue q; + q.push(s); + vis[s] = 1; + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (int to : adj[v]) { + if (!vis[to]) vis[to] = 1, q.push(to); + } + } + return vis[e]; +} + +void t_fix() { + int n = 4, q = 6; + vector op(q + 1), u(q + 1), v(q + 1), ans(q + 1); + op[1] = 1, u[1] = 1, v[1] = 2; + op[2] = 3, u[2] = 1, v[2] = 3; + op[3] = 1, u[3] = 2, v[3] = 3; + op[4] = 3, u[4] = 1, v[4] = 3; + op[5] = 2, u[5] = 1, v[5] = 2; + op[6] = 3, u[6] = 1, v[6] = 2; + + set act; + for (int i = 1; i <= q; i++) { + int a = u[i], b = v[i]; + if (a > b) swap(a, b); + if (op[i] == 1) act.insert({a, b}); + else if (op[i] == 2) act.erase({a, b}); + else ans[i] = conn(n, act, a, b); + } + + odc g; + g.init(n, q); + for (int i = 1; i <= q; i++) g.add_op(i, op[i], u[i], v[i]); + g.build(); + g.run(); + for (int i = 1; i <= q; i++) + if (op[i] == 3) assert(g.ans[i] == ans[i]); +} + +void t_rnd() { + for (int it = 0; it < 200; it++) { + int n = rnd(2, 8); + int q = rnd(1, 40); + vector op(q + 1), u(q + 1), v(q + 1), ans(q + 1); + set act; + for (int i = 1; i <= q; i++) { + int t = rnd(0, 9); + if (act.empty() || t < 4) { + int a = 0, b = 0; + for (int tr = 0; tr < 20; tr++) { + a = rnd(1, n), b = rnd(1, n); + if (a == b) continue; + if (a > b) swap(a, b); + if (!act.count({a, b})) break; + } + if (!a || !b || act.count({a, b})) { + op[i] = 3; + u[i] = rnd(1, n), v[i] = rnd(1, n); + ans[i] = conn(n, act, u[i], v[i]); + } else { + op[i] = 1; + u[i] = a, v[i] = b; + act.insert({a, b}); + } + } else if (t < 7) { + int idx = rnd(0, sz(act) - 1); + auto it2 = act.begin(); + advance(it2, idx); + op[i] = 2; + u[i] = it2->fr; + v[i] = it2->sc; + act.erase(it2); + } else { + op[i] = 3; + u[i] = rnd(1, n), v[i] = rnd(1, n); + ans[i] = conn(n, act, u[i], v[i]); + } + } + + odc g; + g.init(n, q); + for (int i = 1; i <= q; i++) g.add_op(i, op[i], u[i], v[i]); + g.build(); + g.run(); + for (int i = 1; i <= q; i++) + if (op[i] == 3) assert(g.ans[i] == ans[i]); + } +} + +int main() { + t_fix(); + t_rnd(); + return 0; +} diff --git a/tests/2-graph/test_scc_2_sat.cpp b/tests/2-graph/test_scc_2_sat.cpp new file mode 100644 index 0000000..a8b2b63 --- /dev/null +++ b/tests/2-graph/test_scc_2_sat.cpp @@ -0,0 +1,130 @@ +#include "../../src/2-graph/scc_2_sat.cpp" + +// what: tests for scc and 2-sat. +// time: random + brute; memory: O(n^2) +// constraint: small n brute. +// usage: g++ -std=c++17 test_scc_2_sat.cpp && ./a.out + +mt19937_64 rng(4); +int rnd(int l, int r) { + uniform_int_distribution dis(l, r); + return dis(rng); +} + +vector> rch(int n, const vector> &g) { + vector> r(n + 1, vector(n + 1)); + for (int s = 1; s <= n; s++) { + queue q; + q.push(s); + r[s][s] = 1; + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (int to : g[v]) { + if (!r[s][to]) r[s][to] = 1, q.push(to); + } + } + } + return r; +} + +vector cmp_na(int n, const vector> &g) { + auto r = rch(n, g); + vector cmp(n + 1, -1); + int cid = 0; + for (int i = 1; i <= n; i++) { + if (cmp[i] != -1) continue; + for (int j = 1; j <= n; j++) + if (r[i][j] && r[j][i]) cmp[j] = cid; + cid++; + } + return cmp; +} + +bool sat_br(int n, const vector &cl, vector &val) { + int lim = 1 << n; + for (int mask = 0; mask < lim; mask++) { + bool ok = 1; + for (auto [a, b] : cl) { + bool va = (a > 0) ? ((mask >> (a - 1)) & 1) : !((mask >> (-a - 1)) & 1); + bool vb = (b > 0) ? ((mask >> (b - 1)) & 1) : !((mask >> (-b - 1)) & 1); + if (!(va || vb)) { + ok = 0; + break; + } + } + if (!ok) continue; + val.assign(n + 1, 0); + for (int i = 1; i <= n; i++) val[i] = (mask >> (i - 1)) & 1; + return 1; + } + return 0; +} + +void t_scc() { + for (int it = 0; it < 200; it++) { + int n = rnd(1, 7); + int m = rnd(0, n * (n - 1)); + vector> g(n + 1); + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + g[u].push_back(v); + } + auto cmp = cmp_na(n, g); + + scc_ko s1; + s1.init(n); + for (int u = 1; u <= n; u++) + for (int v : g[u]) s1.add(u, v); + s1.run(); + + scc_ta s2; + s2.init(n); + for (int u = 1; u <= n; u++) + for (int v : g[u]) s2.add(u, v); + s2.run(); + + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) { + bool a = (cmp[i] == cmp[j]); + bool b = (s1.comp[i] == s1.comp[j]); + bool c = (s2.comp[i] == s2.comp[j]); + assert(a == b); + assert(a == c); + } + } +} + +void t_sat() { + for (int it = 0; it < 200; it++) { + int n = rnd(1, 8); + int m = rnd(0, 12); + vector cl; + for (int i = 0; i < m; i++) { + int a = rnd(1, n), b = rnd(1, n); + if (rnd(0, 1)) a = -a; + if (rnd(0, 1)) b = -b; + cl.push_back({a, b}); + } + vector val; + bool ok2 = sat_br(n, cl, val); + + two_sat ts; + ts.init(n); + for (auto [a, b] : cl) ts.add(a, b); + bool ok1 = ts.run(); + assert(ok1 == ok2); + if (!ok1) continue; + for (auto [a, b] : cl) { + bool va = a > 0 ? ts.val[a] : !ts.val[-a]; + bool vb = b > 0 ? ts.val[b] : !ts.val[-b]; + assert(va || vb); + } + } +} + +int main() { + t_scc(); + t_sat(); + return 0; +} diff --git a/tests/2-graph/test_shortest_path.cpp b/tests/2-graph/test_shortest_path.cpp new file mode 100644 index 0000000..59e40a3 --- /dev/null +++ b/tests/2-graph/test_shortest_path.cpp @@ -0,0 +1,121 @@ +#include "../../src/2-graph/shortest_path.cpp" + +// what: tests for shortest paths. +// time: random + brute; memory: O(n^2) +// constraint: small n brute. +// usage: g++ -std=c++17 test_shortest_path.cpp && ./a.out + +mt19937_64 rng(3); +int rnd(int l, int r) { + uniform_int_distribution dis(l, r); + return dis(rng); +} + +vector> floy(int n, const vector> &ed) { + const ll INF = (1LL << 60); + vector> d(n + 1, vector(n + 1, INF)); + for (int i = 1; i <= n; i++) d[i][i] = 0; + for (auto [u, v, w] : ed) d[u][v] = min(d[u][v], w); + for (int k = 1; k <= n; k++) + for (int i = 1; i <= n; i++) + if (d[i][k] < INF) + for (int j = 1; j <= n; j++) + if (d[k][j] < INF) + d[i][j] = min(d[i][j], d[i][k] + d[k][j]); + return d; +} + +void t_dijk() { + const ll INF = (1LL << 60); + for (int it = 0; it < 200; it++) { + int n = rnd(2, 7); + int m = rnd(0, n * (n - 1)); + vector> ed; + dijk dj; + dj.init(n); + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + ll w = rnd(0, 9); + ed.push_back({u, v, w}); + dj.add(u, v, w); + } + auto d = floy(n, ed); + int s = rnd(1, n); + auto dist = dj.run(s); + for (int v = 1; v <= n; v++) { + ll a = dist[v]; + ll b = d[s][v]; + if (b >= INF / 2) assert(a >= INF / 2); + else assert(a == b); + } + } +} + +void t_bell() { + const ll INF = (1LL << 60); + for (int it = 0; it < 200; it++) { + int n = rnd(2, 6); + int m = rnd(0, n * (n - 1)); + vector> ed; + bell bl; + bl.init(n); + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + ll w = rnd(-5, 9); + ed.push_back({u, v, w}); + bl.add(u, v, w); + } + auto d = floy(n, ed); + int s = rnd(1, n); + bool neg = 0; + for (int v = 1; v <= n; v++) + if (d[s][v] < INF / 2 && d[v][v] < 0) neg = 1; + vector dist; + bool ok = bl.run(s, dist); + if (neg) { + assert(!ok); + continue; + } + assert(ok); + for (int v = 1; v <= n; v++) { + ll a = dist[v]; + ll b = d[s][v]; + if (b >= INF / 2) assert(a >= INF / 2); + else assert(a == b); + } + } +} + +void t_floy() { + const ll INF = (1LL << 60); + for (int it = 0; it < 200; it++) { + int n = rnd(2, 7); + int m = rnd(0, n * (n - 1)); + vector> ed; + floyd fl; + fl.init(n); + for (int i = 0; i < m; i++) { + int u = rnd(1, n), v = rnd(1, n); + ll w = rnd(-5, 9); + ed.push_back({u, v, w}); + fl.add(u, v, w); + } + auto d = floy(n, ed); + bool neg = 0; + for (int v = 1; v <= n; v++) + if (d[v][v] < 0) neg = 1; + if (neg) continue; + fl.run(); + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) + if (d[i][j] >= INF / 2) assert(fl.d[i][j] >= INF / 2); + else assert(fl.d[i][j] == d[i][j]); + } +} + +int main() { + t_dijk(); + t_bell(); + t_floy(); + return 0; +}