Skip to content

Commit 086ca3a

Browse files
committed
Add TwoSAT
1 parent bcbdf54 commit 086ca3a

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.github.acl4s
2+
3+
/**
4+
* Reference:
5+
* B. Aspvall, M. Plass, and R. Tarjan,
6+
* A Linear-Time Algorithm for Testing the Truth of Certain Quantified Boolean Formulas
7+
*
8+
* @param n
9+
*/
10+
class TwoSAT(private val n: Int) {
11+
val answer = new Array[Boolean](n)
12+
private val scc = io.github.acl4s.internal.SccGraph(2 * n)
13+
14+
def addClause(i: Int, f: Boolean, j: Int, g: Boolean): Unit = {
15+
assert(0 <= i && i < n)
16+
assert(0 <= j && j < n)
17+
scc.addEdge(2 * i + (if (f) { 0 }
18+
else { 1 }),
19+
2 * j + (if (g) { 1 }
20+
else { 0 })
21+
)
22+
scc.addEdge(2 * j + (if (g) { 0 }
23+
else { 1 }),
24+
2 * i + (if (f) { 1 }
25+
else { 0 })
26+
)
27+
}
28+
29+
def satisfiable(): Boolean = {
30+
val (_, id) = scc.sccIds()
31+
(0 until n).foreach(i => {
32+
if (id(2 * i) == id(2 * i + 1)) {
33+
return false
34+
}
35+
answer(i) = id(2 * i) < id(2 * i + 1)
36+
})
37+
true
38+
}
39+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package io.github.acl4s
2+
3+
class TwoSATSuite extends munit.FunSuite {
4+
5+
/**
6+
* @see https://atcoder.jp/contests/practice2/tasks/practice2_h
7+
*/
8+
test("AtCoder Library Practice Contest H - Two SAT") {
9+
val n = 3
10+
val x = Array(1, 2, 0)
11+
val y = Array(4, 5, 6)
12+
13+
{
14+
// Sample Input 1
15+
val d = 2
16+
17+
val ts = TwoSAT(n)
18+
for {
19+
i <- 0 until n
20+
j <- i + 1 until n
21+
} {
22+
if ((x(i) - x(j)).abs < d) {
23+
ts.addClause(i, false, j, false)
24+
}
25+
if ((x(i) - y(j)).abs < d) {
26+
ts.addClause(i, false, j, true)
27+
}
28+
if ((y(i) - x(j)).abs < d) {
29+
ts.addClause(i, true, j, false)
30+
}
31+
if ((y(i) - y(j)).abs < d) {
32+
ts.addClause(i, true, j, true)
33+
}
34+
}
35+
36+
assertEquals(ts.satisfiable(), true)
37+
38+
val res = ts.answer.zipWithIndex
39+
.map((v, i) =>
40+
if (v) { x(i) }
41+
else { y(i) }
42+
)
43+
.sorted
44+
val min_distance = (1 until n).map(i => res(i) - res(i - 1)).min
45+
assert(min_distance >= d)
46+
}
47+
48+
{
49+
// Sample Input 2
50+
val d = 3
51+
52+
val ts = TwoSAT(n)
53+
for {
54+
i <- 0 until n
55+
j <- i + 1 until n
56+
} {
57+
if ((x(i) - x(j)).abs < d) {
58+
ts.addClause(i, false, j, false)
59+
}
60+
if ((x(i) - y(j)).abs < d) {
61+
ts.addClause(i, false, j, true)
62+
}
63+
if ((y(i) - x(j)).abs < d) {
64+
ts.addClause(i, true, j, false)
65+
}
66+
if ((y(i) - y(j)).abs < d) {
67+
ts.addClause(i, true, j, true)
68+
}
69+
}
70+
71+
assertEquals(ts.satisfiable(), false)
72+
}
73+
}
74+
75+
test("zero") {
76+
val ts = TwoSAT(0)
77+
assertEquals(ts.satisfiable(), true)
78+
assertEquals(ts.answer.toSeq, Seq())
79+
}
80+
81+
test("one") {
82+
{
83+
val ts = TwoSAT(1)
84+
ts.addClause(0, true, 0, true)
85+
ts.addClause(0, false, 0, false)
86+
assertEquals(ts.satisfiable(), false)
87+
}
88+
{
89+
val ts = TwoSAT(1)
90+
ts.addClause(0, true, 0, true)
91+
assertEquals(ts.satisfiable(), true)
92+
assertEquals(ts.answer.toSeq, Seq(true))
93+
}
94+
{
95+
val ts = TwoSAT(1)
96+
ts.addClause(0, false, 0, false)
97+
assertEquals(ts.satisfiable(), true)
98+
assertEquals(ts.answer.toSeq, Seq(false))
99+
}
100+
}
101+
102+
test("stress ok") {
103+
(0 until 10_000).foreach(phase => {
104+
val n = randomInt(1, 20)
105+
val m = randomInt(1, 100)
106+
val expect = Array.fill(n)(randomBoolean())
107+
val ts = TwoSAT(n)
108+
109+
val xs = new Array[Int](m)
110+
val ys = new Array[Int](m)
111+
val types = new Array[Int](m)
112+
(0 until m).foreach(i => {
113+
val x = randomInt(0, n - 1)
114+
val y = randomInt(0, n - 1)
115+
val ty = randomInt(0, 2)
116+
xs(i) = x
117+
ys(i) = y
118+
types(i) = ty
119+
ty match {
120+
case 0 => ts.addClause(x, expect(x), y, expect(y))
121+
case 1 => ts.addClause(x, !expect(x), y, expect(y))
122+
case _ => ts.addClause(x, expect(x), y, !expect(y))
123+
}
124+
})
125+
126+
assertEquals(ts.satisfiable(), true)
127+
128+
val actual = ts.answer
129+
(0 until m).foreach(i => {
130+
val x = xs(i)
131+
val y = ys(i)
132+
types(i) match {
133+
case 0 => assertEquals(actual(x) == expect(x) || actual(y) == expect(y), true)
134+
case 1 => assertEquals(actual(x) != expect(x) || actual(y) == expect(y), true)
135+
case _ => assertEquals(actual(x) == expect(x) || actual(y) != expect(y), true)
136+
}
137+
})
138+
})
139+
}
140+
141+
}

src/test/scala/io/github/acl4s/Utils.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.acl4s
22

33
import scala.annotation.tailrec
4+
import scala.util.Random
45

56
@tailrec
67
def gcd(a: Long, b: Long): Long = {
@@ -11,3 +12,9 @@ def gcd(a: Long, b: Long): Long = {
1112
gcd(b, a % b)
1213
}
1314
}
15+
16+
def randomInt(min: Int, max: Int): Int =
17+
Random.between(min, max + 1)
18+
19+
def randomBoolean(): Boolean =
20+
randomInt(0, 1) == 0

0 commit comments

Comments
 (0)