|
1 | 1 | # python3
|
2 | 2 |
|
3 |
| -# Naive Solution: |
4 |
| -# Time Complexity: O(2^n) |
5 |
| -def isSatisfiable_naive(): |
6 |
| - for mask in range(1<<n): |
7 |
| - result = [ (mask >> i) & 1 for i in range(n) ] |
8 |
| - formulaIsSatisfied = True |
9 |
| - for clause in clauses: |
10 |
| - clauseIsSatisfied = False |
11 |
| - if result[abs(clause[0]) - 1] == (clause[0] < 0): |
12 |
| - clauseIsSatisfied = True |
13 |
| - if result[abs(clause[1]) - 1] == (clause[1] < 0): |
14 |
| - clauseIsSatisfied = True |
15 |
| - if not clauseIsSatisfied: |
16 |
| - formulaIsSatisfied = False |
17 |
| - break |
18 |
| - if formulaIsSatisfied: |
19 |
| - return result |
20 |
| - return None |
21 |
| - |
22 |
| -class ImplicationGraph: |
| 3 | +class Implication_graph: |
23 | 4 | def __init__(self, n, clauses):
|
24 |
| - self.adjacency_list = [[] for _ in range(2 * n)] |
25 |
| - self.build_adjacency_list(n, clauses) |
| 5 | + self.sat_variables_count = n |
| 6 | + self.vertices_count = 2 * n |
| 7 | + self.build_adjacency_list(clauses) |
26 | 8 | self.build_reversed_adjacency_list()
|
27 | 9 |
|
28 |
| - def sat_variables_count(self): |
29 |
| - len(self.adjacency_list) // 2 |
30 |
| - |
31 |
| - def vertices_count(self): |
32 |
| - len(self.adjacency_list) |
33 |
| - |
34 | 10 | # Each variable Xi in SAT formula, 2 vertices will be created
|
35 | 11 | # ... Vertice 1 corresponds to Xi and its number is Xi - 1
|
36 | 12 | # ... Vertice 2 corresponds to -Xi and its number is Xi - 1 + Variables count
|
37 | 13 | # E.g. 3 SAT variables: X1, X2, X3: 6 vertices will be created
|
38 | 14 | # Vertices 0 1 2 3 4 5
|
39 | 15 | # Variables X1 X2 X3 -X1 -X2 -X3
|
40 |
| - def get_vertice_num(self, variable_num): |
41 |
| - return variable_num - 1 if variable_num > 0 else (-1) * variable_num - 1 + self.sat_variables_count() |
| 16 | + def vertice_num(self, variable_num): |
| 17 | + return variable_num - 1 if variable_num > 0 else (-1) * variable_num - 1 + self.sat_variables_count |
42 | 18 |
|
| 19 | + def non_vertice_num(self, vertice_num): |
| 20 | + return vertice_num + self.sat_variables_count if vertice_num < self.sat_variables_count else vertice_num - self.sat_variables_count |
| 21 | + |
43 | 22 | # Time Complexity: O(|C|)
|
44 |
| - def build_adjacency_list(self, n, clauses): |
| 23 | + def build_adjacency_list(self, clauses): |
| 24 | + |
| 25 | + self.adjacency_list = [[] for _ in range(2 * self.sat_variables_count)] |
45 | 26 |
|
46 | 27 | for c in clauses:
|
47 | 28 | l = c[0]
|
48 |
| - v_non_l = self.get_vertice_num((-1) * l) |
49 |
| - v_l = self.get_vertice_num(l) |
| 29 | + v_l = self.vertice_num(l) |
| 30 | + v_non_l = self.vertice_num((-1) * l) |
50 | 31 | if len(c) == 1:
|
51 | 32 | # (l): to create an edge from -l to l: -l --> l
|
52 | 33 | self.adjacency_list[v_non_l].append(v_l)
|
53 | 34 |
|
54 | 35 | else:
|
55 | 36 | k = c[1]
|
56 |
| - v_k = self.get_vertice_num(k) |
57 |
| - v_non_k = self.get_vertice_num((-1) * k) |
| 37 | + v_k = self.vertice_num(k) |
| 38 | + v_non_k = self.vertice_num((-1) * k) |
58 | 39 | # (l V k): to create 2 edges from !l to k and !k to l:
|
59 | 40 | # ... !l --> k
|
60 | 41 | # ... !k --> l
|
61 |
| - self.adjacency_list[v_non_l].append(self.get_vertice_num(v_k)) |
62 |
| - self.adjacency_list[v_non_k].append(self.get_vertice_num(v_l)) |
| 42 | + self.adjacency_list[v_non_l].append(v_k) |
| 43 | + self.adjacency_list[v_non_k].append(v_l) |
| 44 | + |
| 45 | + #for v in range(self.vertices_count: |
| 46 | + # print("Vertice V (" + str(v) + ") Adjacency list: ", self.adjacency_list[v]) |
63 | 47 |
|
64 | 48 | # Time Complexity: O(|E|) = O(|Edges|) = O(2 |Clauses|) = O(|C|)
|
65 | 49 | def build_reversed_adjacency_list(self):
|
66 | 50 |
|
67 |
| - self.reversed_adjacency_list = [[] for _ in range(self.vertices_count())] |
| 51 | + self.reversed_adjacency_list = [[] for _ in range(self.vertices_count)] |
68 | 52 |
|
69 |
| - for v in range(self.vertices_count()): |
| 53 | + for v in range(self.vertices_count): |
70 | 54 | for a in self.adjacency_list[v]:
|
71 | 55 | self.reversed_adjacency_list[a].append(v)
|
| 56 | + |
| 57 | + #for v in range(self.vertices_count: |
| 58 | + # print("Vertice V (" + str(v) + ") Reversed Adjacency list: ", self.reversed_adjacency_list[v]) |
| 59 | + |
| 60 | + def explore(self, v, an_adjacency_list, preorder_visits, postorder_visits): |
| 61 | + |
| 62 | + self.visited[v] = True |
| 63 | + if preorder_visits != None: |
| 64 | + preorder_visits.append(v) |
| 65 | + |
| 66 | + for a in an_adjacency_list[v]: |
| 67 | + if not self.visited[a]: |
| 68 | + self.explore(a, an_adjacency_list, preorder_visits, postorder_visits) |
| 69 | + |
| 70 | + if postorder_visits != None: |
| 71 | + postorder_visits.append(v) |
| 72 | + |
| 73 | + def dfs(self, an_adjacency_list, dfs_preorder, dfs_postorder): |
| 74 | + self.visited = [False] * self.vertices_count |
| 75 | + |
| 76 | + for v in range(self.vertices_count): |
| 77 | + if not self.visited[v]: |
| 78 | + self.explore(v, an_adjacency_list, dfs_preorder, dfs_postorder) |
| 79 | + |
| 80 | + def stronly_connected_components(self): |
| 81 | + |
| 82 | + dfs_postorder = [] |
| 83 | + |
| 84 | + # 1. Find postorder in Gr |
| 85 | + # ... Source vertices in Gr have the highest postorder |
| 86 | + # ... A source vertice in Gr is Sink vertice in G |
| 87 | + self.dfs(self.reversed_adjacency_list, None, dfs_postorder) |
| 88 | + print("dfs_postorder: ", dfs_postorder) |
| 89 | + |
| 90 | + # 2. Find SCCs in G |
| 91 | + sccs = [] |
| 92 | + self.visited = [False] * self.vertices_count |
| 93 | + for i in range(len(dfs_postorder) - 1, -1, -1): |
| 94 | + v = dfs_postorder[i] |
| 95 | + if not self.visited[v]: |
| 96 | + sccs.append([]) |
| 97 | + self.explore(v, self.adjacency_list, None, sccs[len(sccs) - 1]) |
| 98 | + |
| 99 | + return sccs |
| 100 | + |
| 101 | + def solve_2sat(self): |
| 102 | + |
| 103 | + strongly_connected_components = self.stronly_connected_components() |
| 104 | + |
| 105 | + # sccs is ordered in reversed topological order |
| 106 | + vertice_assignment = [-1] * self.vertices_count |
| 107 | + for scc in strongly_connected_components: |
| 108 | + for v in scc: |
| 109 | + if vertice_assignment[v] == -1: |
| 110 | + vertice_assignment[v] = 1 |
| 111 | + |
| 112 | + non_v = self.non_vertice_num(v) |
| 113 | + if vertice_assignment[non_v] == -1: |
| 114 | + vertice_assignment[non_v] = 0 |
| 115 | + |
| 116 | + if vertice_assignment[non_v] == 1: |
| 117 | + return None |
| 118 | + |
| 119 | + print("sccs: ", strongly_connected_components) |
| 120 | + print("vertice_assignment: ", vertice_assignment) |
| 121 | + return vertice_assignment[:self.sat_variables_count] |
| 122 | + |
| 123 | + |
| 124 | +def isSatisfiable(n, clauses): |
| 125 | + |
| 126 | + g = Implication_graph(n, clauses) |
72 | 127 |
|
| 128 | + return g.solve_2sat() |
| 129 | + |
| 130 | +# Naive Solution: |
| 131 | +# Time Complexity: O(2^n) |
| 132 | +def isSatisfiable_naive(n, clauses): |
| 133 | + for mask in range(1<<n): |
| 134 | + result = [ (mask >> i) & 1 for i in range(n) ] |
| 135 | + formulaIsSatisfied = True |
| 136 | + for clause in clauses: |
| 137 | + clauseIsSatisfied = False |
| 138 | + if result[abs(clause[0]) - 1] == (clause[0] < 0): |
| 139 | + clauseIsSatisfied = True |
| 140 | + if result[abs(clause[1]) - 1] == (clause[1] < 0): |
| 141 | + clauseIsSatisfied = True |
| 142 | + if not clauseIsSatisfied: |
| 143 | + formulaIsSatisfied = False |
| 144 | + break |
| 145 | + if formulaIsSatisfied: |
| 146 | + return result |
| 147 | + return None |
| 148 | + |
73 | 149 | if __name__ == "__main__":
|
74 | 150 |
|
75 | 151 | n, m = map(int, input().split())
|
76 | 152 | clauses = [ list(map(int, input().split())) for i in range(m) ]
|
77 | 153 |
|
78 |
| - result = isSatisfiable_naive() |
| 154 | + #result = isSatisfiable_naive(n, clauses) |
| 155 | + result = isSatisfiable(n, clauses) |
| 156 | + |
79 | 157 | if result is None:
|
80 | 158 | print("UNSATISFIABLE")
|
81 | 159 | else:
|
82 | 160 | print("SATISFIABLE")
|
83 |
| - print(" ".join(str(-i-1 if result[i] else i+1) for i in range(n))) |
| 161 | + print(" ".join(str(i + 1 if result[i] else -i-1) for i in range(n))) |
| 162 | + #print(" ".join(str(-i-1 if result[i] else i+1) for i in range(n))) |
0 commit comments