From c6532fcbeacbf866d30fedacab5f67099b54be93 Mon Sep 17 00:00:00 2001 From: suisen-cp Date: Thu, 10 Sep 2020 01:13:29 +0900 Subject: [PATCH 1/4] SCC --- SCC/Readme.md | 50 ++++++++++++++++++++++ SCC/SCC.java | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 SCC/Readme.md create mode 100644 SCC/SCC.java diff --git a/SCC/Readme.md b/SCC/Readme.md new file mode 100644 index 0000000..854eaba --- /dev/null +++ b/SCC/Readme.md @@ -0,0 +1,50 @@ +# クラス SCC (Strongly Connected Components: 強連結成分分解) + +有向グラフを強連結成分分解します。 + +## コンストラクタ + +```java +SCC(int n) +``` + +`n` 頂点 `0` 辺の有向グラフを作る。 + +計算量 + +$O(n)$ + +## メソッド + +### addEdge + +```java +public void addEdge(int from, int to) +``` + +頂点 `from` から頂点 `to` へ有向辺を足す。 + +制約 + +- `0 <= from < n` +- `0 <= to < n` + +計算量 + +amortized $O(1)$ + +### build + +```java +public int[][] build() +``` + +以下の条件を満たすような、「頂点の配列」の配列を返します。 + +- 全ての頂点がちょうど 1 つずつ、どれかのリストに含まれます。 +- 内側のリストと強連結成分が一対一に対応します。リスト内での頂点の順序は未定義です。 +- 配列はトポロジカルソートされています。異なる強連結成分の頂点 `u`, `v` について、`u` から `v` に到達できる時、`u` の属する配列は `v` の属する配列よりも前です。 + +計算量 + +追加した辺の本数を `m` として $O(n + m)$ diff --git a/SCC/SCC.java b/SCC/SCC.java new file mode 100644 index 0000000..ea9ce09 --- /dev/null +++ b/SCC/SCC.java @@ -0,0 +1,115 @@ +class SCC { + + static class Edge { + int from, to; + public Edge(int from, int to) { + this.from = from; this.to = to; + } + } + + final int n; + int m; + final java.util.ArrayList unorderedEdges; + final int[] start; + + public SCC(int n) { + this.n = n; + this.unorderedEdges = new java.util.ArrayList<>(); + this.start = new int[n + 1]; + } + + public void addEdge(int from, int to) { + unorderedEdges.add(new Edge(from, to)); + start[from + 1]++; + this.m++; + } + + public int[][] build() { + for (int i = 1; i <= n; i++) { + start[i] += start[i - 1]; + } + Edge[] orderedEdges = new Edge[m]; + int[] count = new int[n + 1]; + System.arraycopy(start, 0, count, 0, n + 1); + for (Edge e : unorderedEdges) { + orderedEdges[count[e.from]++] = e; + } + int nowOrd = 0; + int groupNum = 0; + int k = 0; + // parent + int[] par = new int[n]; + int[] vis = new int[n]; + int[] low = new int[n]; + int[] ord = new int[n]; + int[] ids = new int[n]; + java.util.Arrays.fill(ord, -1); + // u = lower32(stack[i]) : visiting vertex + // j = upper32(stack[i]) : jth child + long[] stack = new long[n]; + // size of stack + int ptr = 0; + // non-recursional DFS + for (int i = 0; i < n; i++) { + if (ord[i] >= 0) continue; + par[i] = -1; + // vertex i, 0th child. + stack[ptr++] = 0l << 32 | i; + // stack is not empty + while (ptr > 0) { + // last element + long p = stack[--ptr]; + // vertex + int u = (int) (p & 0xffff_ffffl); + // jth child + int j = (int) (p >>> 32); + if (j == 0) { // first visit + low[u] = ord[u] = nowOrd++; + vis[k++] = u; + } + if (start[u] + j < count[u]) { // there are more children + // jth child + int to = orderedEdges[start[u] + j].to; + // incr children counter + stack[ptr++] += 1l << 32; + if (ord[to] == -1) { // new vertex + stack[ptr++] = 0l << 32 | to; + par[to] = u; + } else { // backward edge + low[u] = Math.min(low[u], ord[to]); + } + } else { // no more children (leaving) + while (j --> 0) { + int to = orderedEdges[start[u] + j].to; + // update lowlink + if (par[to] == u) low[u] = Math.min(low[u], low[to]); + } + if (low[u] == ord[u]) { // root of a component + while (true) { // gathering verticies + int v = vis[--k]; + ord[v] = n; + ids[v] = groupNum; + if (v == u) break; + } + groupNum++; // incr the number of components + } + } + } + } + for (int i = 0; i < n; i++) { + ids[i] = groupNum - 1 - ids[i]; + } + + int[] counts = new int[groupNum]; + for (int x : ids) counts[x]++; + int[][] groups = new int[groupNum][]; + for (int i = 0; i < groupNum; i++) { + groups[i] = new int[counts[i]]; + } + for (int i = 0; i < n; i++) { + int cmp = ids[i]; + groups[cmp][--counts[cmp]] = i; + } + return groups; + } +} \ No newline at end of file From e5422cf9027a2323395172bcf05d275c913b1445 Mon Sep 17 00:00:00 2001 From: suisen-cp Date: Thu, 10 Sep 2020 07:48:54 +0900 Subject: [PATCH 2/4] 2 SAT --- 2SAT/Readme.md | 110 +++++++++++++++++++++++++++++++++++ 2SAT/TwoSAT.java | 146 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 2SAT/Readme.md create mode 100644 2SAT/TwoSAT.java diff --git a/2SAT/Readme.md b/2SAT/Readme.md new file mode 100644 index 0000000..86b9b3d --- /dev/null +++ b/2SAT/Readme.md @@ -0,0 +1,110 @@ +# クラス TwoSAT + +2-SATを解きます。 変数 `x[0], x[1], ⋯, x[N - 1]` に関して、 + +`(x[i] = f) ∨ (x[j] = g)` + +というクローズを足し、これをすべて満たす変数の割当があるかを解きます。 + +## コンストラクタ + +```java +public TwoSAT(int n) +``` + +`n` 変数の2-SATを作ります。 + +計算量 + +$O(n)$ + +## メソッド + +### addClause + +```java +public void addClause(int x, boolean f, int y, boolean g) +``` + +`(x[i] = f) ∨ (x[j] = g)` というクローズを足します。 + +制約 + +- `0 <= i < n` +- `0 <= j < n` + +計算量 + +ならし $O(1)$ + +### addImplication + +```java +public void addImplication(int x, boolean f, int y, boolean g) +``` + +`(x[i] = f) → (x[j] = g)`, 即ち `(x[i] = !f) ∨ (x[j] = g)` というクローズを足します。 + +制約 + +- `0 <= i < n` +- `0 <= j < n` + +計算量 + +ならし $O(1)$ + +### addNand + +```java +public void addNand(int x, boolean f, int y, boolean g) +``` + +`!((x[i] = f) ∧ (x[j] = g))`, 即ち `(x[i] = !f) ∨ (x[j] = !g)` というクローズを足します。禁止制約の追加と考えるとよいです。 + +制約 + +- `0 <= i < n` +- `0 <= j < n` + +計算量 + +ならし $O(1)$ + +### satisfiable + +```java +public boolean satisfiable() +``` + +条件を足す割当が存在するかどうかを判定する。割当が存在するならば `true`、そうでないなら `false` を返す。 + +制約 + +- 複数回呼ぶことも可能。 + +計算量 + +足した制約の個数を `m` として $O(n+m)$ + +### answer + +```java +public java.util.BitSet answer() +``` + +`satisfiable` を最後に呼んだ時点での、クローズを満たす割当を返す。割当が存在しなかった場合は `null` を返す。 + +__`satisfiable` を一度も呼んでいない時点で呼ばれた場合は、実行時例外 `UnsupportedOperationException` が発生します (`satisfiable` の呼び忘れ防止)。__ + +制約 + +- __`satisfiable` を少なくとも 1 回は呼んでいる__ + +計算量 + +$O(n)$ + +## 使用例 + +[AtCoder Library Practice Contest H - Two SAT](https://atcoder.jp/contests/practice2/submissions/16603939) diff --git a/2SAT/TwoSAT.java b/2SAT/TwoSAT.java new file mode 100644 index 0000000..1bd05b0 --- /dev/null +++ b/2SAT/TwoSAT.java @@ -0,0 +1,146 @@ +class TwoSAT { + private final int n; + private final InternalSCC scc; + private final java.util.BitSet answer; + + private boolean hasCalledSatisfiable = false; + private boolean existsAnswer = false; + + public TwoSAT(int n) { + this.n = n; + this.scc = new InternalSCC(2 * n); + this.answer = new java.util.BitSet(n); + } + + public void addClause(int x, boolean f, int y, boolean g) { + scc.addEdge(x << 1 | (f ? 0 : 1), y << 1 | (g ? 1 : 0)); + scc.addEdge(y << 1 | (g ? 0 : 1), x << 1 | (f ? 1 : 0)); + } + + public void addImplication(int x, boolean f, int y, boolean g) { + addClause(x, !f, y, g); + } + + public void addNand(int x, boolean f, int y, boolean g) { + addClause(x, !f, y, !g); + } + + public boolean satisfiable() { + hasCalledSatisfiable = true; + int[] ids = scc.ids(); + for (int i = 0; i < n; i++) { + if (ids[i << 1 | 0] == ids[i << 1 | 1]) return existsAnswer = false; + answer.set(i, ids[i << 1 | 0] < ids[i << 1 | 1]); + } + return existsAnswer = true; + } + + public java.util.BitSet answer() { + if (!hasCalledSatisfiable) { + throw new UnsupportedOperationException( + "Call TwoSAT#satisfiable at least once before TwoSAT#answer." + ); + } + if (existsAnswer) return answer; + return null; + } + + private static final class EdgeList { + long[] a; + int ptr = 0; + EdgeList(int cap) {a = new long[cap];} + void add(int upper, int lower) { + if (ptr == a.length) grow(); + a[ptr++] = (long) upper << 32 | lower; + } + void grow() { + long[] b = new long[a.length << 1]; + System.arraycopy(a, 0, b, 0, a.length); + a = b; + } + } + + private static final class InternalSCC { + final int n; + int m; + final EdgeList unorderedEdges; + final int[] start; + InternalSCC(int n) { + this.n = n; + this.unorderedEdges = new EdgeList(n); + this.start = new int[n + 1]; + } + void addEdge(int from, int to) { + unorderedEdges.add(from, to); + start[from + 1]++; + this.m++; + } + static final long mask = 0xffff_ffffl; + int[] ids() { + for (int i = 1; i <= n; i++) { + start[i] += start[i - 1]; + } + int[] orderedEdges = new int[m]; + int[] count = new int[n + 1]; + System.arraycopy(start, 0, count, 0, n + 1); + for (int i = 0; i < m; i++) { + long e = unorderedEdges.a[i]; + orderedEdges[count[(int) (e >>> 32)]++] = (int) (e & mask); + } + int nowOrd = 0; + int groupNum = 0; + int k = 0; + int[] par = new int[n]; + int[] vis = new int[n]; + int[] low = new int[n]; + int[] ord = new int[n]; + java.util.Arrays.fill(ord, -1); + int[] ids = new int[n]; + long[] stack = new long[n]; + int ptr = 0; + + for (int i = 0; i < n; i++) { + if (ord[i] >= 0) continue; + par[i] = -1; + stack[ptr++] = i; + while (ptr > 0) { + long p = stack[--ptr]; + int u = (int) (p & mask); + int j = (int) (p >>> 32); + if (j == 0) { + low[u] = ord[u] = nowOrd++; + vis[k++] = u; + } + if (start[u] + j < count[u]) { + int to = orderedEdges[start[u] + j]; + stack[ptr++] += 1l << 32; + if (ord[to] == -1) { + stack[ptr++] = to; + par[to] = u; + } else { + low[u] = Math.min(low[u], ord[to]); + } + } else { + while (j --> 0) { + int to = orderedEdges[start[u] + j]; + if (par[to] == u) low[u] = Math.min(low[u], low[to]); + } + if (low[u] == ord[u]) { + while (true) { + int v = vis[--k]; + ord[v] = n; + ids[v] = groupNum; + if (v == u) break; + } + groupNum++; + } + } + } + } + for (int i = 0; i < n; i++) { + ids[i] = groupNum - 1 - ids[i]; + } + return ids; + } + } +} From 4affbac52a599b78f2fdfe14821f0fd4939e27a6 Mon Sep 17 00:00:00 2001 From: suisen-cp Date: Thu, 10 Sep 2020 07:55:36 +0900 Subject: [PATCH 3/4] =?UTF-8?q?SCC:=20SCC#id=20=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?.=20Readme=20=E3=81=AB=E4=BD=BF=E7=94=A8=E4=BE=8B=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SCC/Readme.md | 23 +++++++++++++++++++++++ SCC/SCC.java | 14 +++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/SCC/Readme.md b/SCC/Readme.md index 854eaba..013a816 100644 --- a/SCC/Readme.md +++ b/SCC/Readme.md @@ -48,3 +48,26 @@ public int[][] build() 計算量 追加した辺の本数を `m` として $O(n + m)$ + +### comp + +```java +public int id(int i) +``` + +頂点 `i` が (トポロジカル順序において) 何番目の強連結成分に属しているかを返します。即ち,`build` で得られる「頂点の配列」の配列を `g` とすると、`g[id(i)][j] = i` なる `j` が存在します。 + +__ただし、`build` を呼ぶ前にこのメソッドが呼ばれた場合は、実行時例外 `UnsupportedOperationException` が発生します。__ + +制約 + +- __`build` メソッドを既に呼んでいる__ +- `0 <= i < n` + +計算量 + +$O(1)$ + +## 使用例 + +[AtCoder Library Practice Contest G - SCC](https://atcoder.jp/contests/practice2/submissions/16603978) diff --git a/SCC/SCC.java b/SCC/SCC.java index ea9ce09..81dd1aa 100644 --- a/SCC/SCC.java +++ b/SCC/SCC.java @@ -11,11 +11,14 @@ public Edge(int from, int to) { int m; final java.util.ArrayList unorderedEdges; final int[] start; + final int[] ids; + boolean hasBuilt = false; public SCC(int n) { this.n = n; this.unorderedEdges = new java.util.ArrayList<>(); this.start = new int[n + 1]; + this.ids = new int[n]; } public void addEdge(int from, int to) { @@ -23,6 +26,15 @@ public void addEdge(int from, int to) { start[from + 1]++; this.m++; } + + public int id(int i) { + if (!hasBuilt) { + throw new UnsupportedOperationException( + "Graph hasn't been built." + ); + } + return ids[i]; + } public int[][] build() { for (int i = 1; i <= n; i++) { @@ -42,7 +54,6 @@ public int[][] build() { int[] vis = new int[n]; int[] low = new int[n]; int[] ord = new int[n]; - int[] ids = new int[n]; java.util.Arrays.fill(ord, -1); // u = lower32(stack[i]) : visiting vertex // j = upper32(stack[i]) : jth child @@ -110,6 +121,7 @@ public int[][] build() { int cmp = ids[i]; groups[cmp][--counts[cmp]] = i; } + hasBuilt = true; return groups; } } \ No newline at end of file From fba3f508bc565bbd4e7c275f6dd1be2f5727e808 Mon Sep 17 00:00:00 2001 From: suisen-cp Date: Thu, 10 Sep 2020 07:58:01 +0900 Subject: [PATCH 4/4] =?UTF-8?q?SCC:=20Readme=20=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SCC/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCC/Readme.md b/SCC/Readme.md index 013a816..da2d737 100644 --- a/SCC/Readme.md +++ b/SCC/Readme.md @@ -49,7 +49,7 @@ public int[][] build() 追加した辺の本数を `m` として $O(n + m)$ -### comp +### id ```java public int id(int i)