Löse 2-SAT (Boolesche Erfüllbarkeit)

16

Das allgemeine SAT- Problem (Boolesche Erfüllbarkeit) ist NP-vollständig. Aber 2-SAT , wobei jede Klausel nur zwei Variablen hat, ist in P . Schreiben Sie einen Löser für 2-SAT.

Eingang:

Eine 2-SAT-Instanz, die wie folgt in CNF codiert ist . Die erste Zeile enthält V, die Anzahl der Booleschen Variablen und N, die Anzahl der Klauseln. Dann folgen N Zeilen mit jeweils 2 Ganzzahlen ungleich Null, die die Literale einer Klausel darstellen. Positive Ganzzahlen repräsentieren die angegebene boolesche Variable und negative Ganzzahlen repräsentieren die Negation der Variablen.

Beispiel 1

Eingang

4 5
1 2
2 3
3 4
-1 -3
-2 -4

welches die Formel (x 1 oder x 2 ) und (x 2 oder x 3 ) und (x 3 oder x 4 ) und (nicht x 1 oder nicht x 3 ) und (nicht x 2 oder nicht x 4 ) codiert .

Die einzige Einstellung der 4 Variablen, die die gesamte Formel wahr macht, ist x 1 = falsch, x 2 = wahr, x 3 = wahr, x 4 = falsch , daher sollte Ihr Programm die einzelne Zeile ausgeben

Ausgabe

0 1 1 0

Darstellen der Wahrheitswerte der V-Variablen (in der Reihenfolge von x 1 bis x V ). Wenn es mehrere Lösungen gibt, können Sie jede nicht leere Teilmenge davon ausgeben, eine pro Zeile. Wenn es keine Lösung gibt, müssen Sie ausgeben UNSOLVABLE.

Beispiel 2

Eingang

2 4
1 2
-1 2
-2 1
-1 -2

Ausgabe

UNSOLVABLE

Beispiel 3

Eingang

2 4
1 2
-1 2
2 -1
-1 -2

Ausgabe

0 1

Beispiel 4

Eingang

8 12
1 4
-2 5
3 7
2 -5
-8 -2
3 -1
4 -3
5 -4
-3 -7
6 7
1 7
-7 -1

Ausgabe

1 1 1 1 1 1 0 0
0 1 0 1 1 0 1 0
0 1 0 1 1 1 1 0

(oder jede nicht leere Teilmenge dieser 3 Zeilen)

Ihr Programm muss alle N, V ​​<100 in angemessener Zeit verarbeiten. Probieren Sie dieses Beispiel aus , um sicherzustellen, dass Ihr Programm eine große Instanz verarbeiten kann. Das kleinste Programm gewinnt.

Keith Randall
quelle
Sie erwähnen, dass 2-SAT in P ist, aber nicht, dass die Lösung in Polynomialzeit ausgeführt werden muss
;-)
@ Timwi: Nein, aber es muss V = 99 in einer angemessenen Zeit behandeln ...
Keith Randall

Antworten:

4

Haskell, 278 Zeichen

(∈)=elem
r v[][]=[(>>=(++" ").show.fromEnum.(∈v))]
r v[]c@(a:b:_)=r(a:v)c[]++r(-a:v)c[]++[const"UNSOLVABLE"]
r v(a:b:c)d|a∈v||b∈v=r v c d|(-a)∈v=i b|(-b)∈v=i a|1<3=r v c(a:b:d)where i w|(-w)∈v=[]|1<3=r(w:v)(c++d)[]
t(n:_:c)=(r[][]c!!0)[1..n]++"\n"
main=interact$t.map read.words

Keine rohe Gewalt. Läuft in Polynomzeit. Löst das schwierige Problem (60 Variablen, 99 Klauseln) schnell:

> time (runhaskell 1933-2Sat.hs < 1933-hard2sat.txt)
1 1 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 1 0 1 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1 0 0 0 0 1 0 1 1 1 1 0 

real 0m0.593s
user 0m0.502s
sys  0m0.074s

Und tatsächlich wird die meiste Zeit damit verbracht, den Code zu kompilieren!

Vollquelldatei, mit Testfällen und Quick-Check - Tests zur Verfügung .

Ungolf'd:

-- | A variable or its negation
-- Note that applying unary negation (-) to a term inverts it.
type Term = Int

-- | A set of terms taken to be true.
-- Should only contain  a variable or its negation, never both.
type TruthAssignment = [Term]

-- | Special value indicating that no consistent truth assignment is possible.
unsolvable :: TruthAssignment
unsolvable = [0]

-- | Clauses are a list of terms, taken in pairs.
-- Each pair is a disjunction (or), the list as a whole the conjuction (and)
-- of the pairs.
type Clauses = [Term]

-- | Test to see if a term is in an assignment
(∈) :: Term -> TruthAssignment -> Bool
a∈v = a `elem` v;

-- | Satisfy a set of clauses, from a starting assignment.
-- Returns a non-exhaustive list of possible assignments, followed by
-- unsolvable. If unsolvable is first, there is no possible assignment.
satisfy :: TruthAssignment -> Clauses -> [TruthAssignment]
satisfy v c@(a:b:_) = reduce (a:v) c ++ reduce (-a:v) c ++ [unsolvable]
  -- pick a term from the first clause, either it or its negation must be true;
  -- if neither produces a viable result, then the clauses are unsolvable
satisfy v [] = [v]
  -- if there are no clauses, then the starting assignment is a solution!

-- | Reduce a set of clauses, given a starting assignment, then solve that
reduce :: TruthAssignment -> Clauses -> [TruthAssignment]
reduce v c = reduce' v c []
  where
    reduce' v (a:b:c) d
        | a∈v || b∈v = reduce' v c d
            -- if the clause is already satisfied, then just drop it
        | (-a)∈v = imply b
        | (-b)∈v = imply a
            -- if either term is not true, the other term must be true
        | otherwise = reduce' v c (a:b:d)
            -- this clause is still undetermined, save it for later
        where 
          imply w
            | (-w)∈v = []  -- if w is also false, there is no possible solution
            | otherwise = reduce (w:v) (c++d)
                -- otherwise, set w true, and reduce again
    reduce' v [] d = satisfy v d
        -- once all caluses have been reduced, satisfy the remaining

-- | Format a solution. Terms not assigned are choosen to be false
format :: Int -> TruthAssignment -> String
format n v
    | v == unsolvable = "UNSOLVABLE"
    | otherwise = unwords . map (bit.(∈v)) $ [1..n]
  where
    bit False = "0"
    bit True = "1"

main = interact $ run . map read . words 
  where
    run (n:_:c) = (format n $ head $ satisfy [] c) ++ "\n"
        -- first number of input is number of variables
        -- second number of input is number of claues, ignored
        -- remaining numbers are the clauses, taken two at a time

In der golf'd Version, satisfyund formatwurde in gerollt reduce, wenn auch um zu vermeiden , beiläufig n, reduceeine Funktion aus einer Liste von Variablen (zurück [1..n]) auf das String Ergebnis.


  • Edit: (330 -> 323) sOperator gemacht, besseres Handling der Newline
  • Bearbeiten: (323 -> 313) Das erste Element aus einer verzögerten Ergebnisliste ist kleiner als ein benutzerdefinierter Kurzschlussoperator. Hauptlöserfunktion umbenannt, weil ich gerne als Operator benutze !
  • Edit: (313 -> 296) keep-Klauseln als einzelne Liste, nicht als Liste von Listen; Verarbeiten Sie zwei Elemente gleichzeitig
  • Editieren: (296 -> 291) fügte die beiden gegenseitig rekursiven Funktionen zusammen; es war billiger inline zu testen also jetzt umbenannt
  • Bearbeiten: (291 -> 278) Inline-Ausgabeformatierung für die Ergebnisgenerierung
MtnViewMark
quelle
4

J 119, 103

echo'UNSOLVABLE'"_`(#&c)@.(*@+/)(3 :'*./+./"1(*>:*}.i)=y{~"1 0<:|}.i')"1 c=:#:i.2^{.,i=:0&".;._2(1!:1)3
  • Besteht alle Testfälle. Keine merkliche Laufzeit.
  • Rohe Gewalt. Besteht folgende Testfälle, oh, N = 20 oder 30. Nicht sicher.
  • Getestet über ein vollständig gehirntotes Testskript (durch Sichtprüfung)

Edit: Ausgeschieden (n#2)und damit n=:auch einige Rangparens (danke, isawdrones). Tacit-> explizit und dyadisch-> monadisch, wobei jeweils einige weitere Zeichen eliminiert werden. }.}.zu }.,.

Bearbeiten: Whoops. Dies ist nicht nur eine Nichtlösung für große N, sondern i. 2^99x-> "Domänenfehler", um die Dummheit zu beleidigen.

Hier ist die unbespielte Originalversion und eine kurze Erklärung.

input=:0&".;._2(1!:1)3
n =:{.{.input
clauses=:}.input
cases=:(n#2)#:i.2^n
results =: clauses ([:*./[:+./"1*@>:@*@[=<:@|@[{"(0,1)])"(_,1) cases
echo ('UNSOLVABLE'"_)`(#&cases) @.(*@+/) results
  • input=:0&".;._2(1!:1)3 Schneidet Eingaben in Zeilenumbrüchen ab und analysiert die Zahlen in jeder Zeile (Summierung der Ergebnisse in Eingaben).
  • n ist zugewiesen n, Klauselmatrix ist zugewiesen clauses(die Klauselzählung ist nicht erforderlich)
  • casesist 0..2 n -1 konvertiert in binäre Ziffern (alle Testfälle)
  • (Long tacit function)"(_,1)wird auf jeden fall casesbei allen angewendet clauses.
  • <:@|@[{"(0,1)] Ruft eine Matrix der Operanden der Klauseln ab (indem abs (op number) - 1 genommen und aus case, einem Array, dereferenziert wird)
  • *@>:@*@[ Erhält eine klauselförmige Anordnung von Nicht-Nicht-Bits (0 für Nicht) durch Missbrauch des Zeichens.
  • = Wendet die Nicht-Bits auf die Operanden an.
  • [:*./[:+./"1Wendet +.(und) auf die Zeilen der resultierenden Matrix und *.(oder) auf das Ergebnis davon an.
  • All diese Ergebnisse ergeben eine binäre Anordnung von "Antworten" für jeden Fall.
  • *@+/ Auf Ergebnisse angewendet ergibt 0, wenn es Ergebnisse gibt, und 1, wenn es keine gibt.
  • ('UNSOLVABLE'"_) `(#&cases) @.(*@+/) results Führt eine konstante Funktion aus, die "UNSOLVABLE" bei 0 und eine Kopie jedes "solution" -Elements von Fällen bei 1 angibt.
  • echo Magie druckt das Ergebnis.
Jesse Millikan
quelle
Sie können die Parens um die Rangargumente entfernen. "(_,1)zu "_ 1. #:würde ohne das linke Argument funktionieren.
Isawdrones
@isawdrones: Ich denke, die traditionelle Antwort wäre, meinen Geist zu zerschlagen, indem ich eine Antwort produziere, die halb so lang ist. "Schreien und springen", wie der Kzin sagen würde. Aber danke, das eliminiert 10 ungerade Zeichen ... Ich könnte unter 100 werden, wenn ich darauf zurückkomme.
Jesse Millikan
+1 für die nette und ausführliche Erklärung, sehr faszinierend zu lesen!
Timwi
Wahrscheinlich wird N = V = 99 nicht in angemessener Zeit behandelt. Versuchen Sie das große Beispiel, das ich gerade hinzugefügt habe.
Keith Randall
3

K - 89

Die gleiche Methode wie die J-Lösung.

n:**c:.:'0:`;`0::[#b:t@&&/+|/''(0<'c)=/:(t:+2_vs!_2^n)@\:-1+_abs c:1_ c;5:b;"UNSOLVABLE"]
isawdrones
quelle
Schön, ich wusste nicht, dass es eine kostenlose K-Implementierung gibt.
Jesse Millikan
Wahrscheinlich wird N = V = 99 nicht in angemessener Zeit behandelt. Versuchen Sie das große Beispiel, das ich gerade hinzugefügt habe.
Keith Randall
2

Rubin, 253

n,v=gets.split;d=[];v.to_i.times{d<<(gets.split.map &:to_i)};n=n.to_i;r=[1,!1]*n;r.permutation(n){|x|y=x[0,n];x=[0]+y;puts y.map{|z|z||0}.join ' 'or exit if d.inject(1){|t,w|t and(w[0]<0?!x[-w[0]]:x[w[0]])||(w[1]<0?!x[-w[1]]:x[w[1]])}};puts 'UNSOLVABLE'

Aber es ist langsam :(

Ziemlich gut lesbar, wenn erweitert:

n,v=gets.split
d=[]
v.to_i.times{d<<(gets.split.map &:to_i)} # read data
n=n.to_i
r=[1,!1]*n # create an array of n trues and n falses
r.permutation(n){|x| # for each permutation of length n
    y=x[0,n]
    x=[0]+y
    puts y.map{|z| z||0}.join ' ' or exit if d.inject(1){|t,w| # evaluate the data (magic!)
        t and (w[0]<0 ? !x[-w[0]] : x[w[0]]) || (w[1]<0 ? !x[-w[1]] : x[w[1]])
    }
}
puts 'UNSOLVABLE'
Matma Rex
quelle
Wahrscheinlich wird N = V = 99 nicht in angemessener Zeit behandelt. Versuchen Sie das große Beispiel, das ich gerade hinzugefügt habe.
Keith Randall
1

OCaml + Batterien, 438 436 Zeichen

Benötigt eine OCaml-Batterie

module L=List
let(%)=L.mem
let rec r v d c n=match d,c with[],[]->[String.join" "[?L:if x%v
then"1"else"0"|x<-1--n?]]|[],(x,_)::_->r(x::v)c[]n@r(-x::v)c[]n@["UNSOLVABLE"]|(x,y)::c,d->let(!)w=if-w%v
then[]else r(w::v)(c@d)[]n in if x%v||y%v then r v c d n else if-x%v then!y else if-y%v then!x else r v c((x,y)::d)n
let(v,_)::l=L.of_enum(IO.lines_of stdin|>map(fun s->Scanf.sscanf s"%d %d"(fun x y->x,y)))in print_endline(L.hd(r[][]l v))

Ich muss gestehen, dies ist eine direkte Übersetzung der Haskell-Lösung. In meiner Verteidigung ist , dass wiederum ein direkter des Algorithmus Codierung hier präsentiert [PDF], mit der gegenseitigen satisfy- eliminateRekursion in einer einzigen Funktion gerollt. Eine nicht verschleierte Version des Codes ohne die Verwendung von Batterien lautet:

let rec satisfy v c d = match c, d with
| (x, y) :: c, d ->
    let imply w = if List.mem (-w) v then raise Exit else satisfy (w :: v) (c @ d) [] in
    if List.mem x v || List.mem y v then satisfy v c d else
    if List.mem (-x) v then imply y else
    if List.mem (-y) v then imply x else
    satisfy v c ((x, y) :: d)
| [], [] -> v
| [], (x, _) :: _ -> try satisfy (x :: v) d [] with Exit -> satisfy (-x :: v) d []

let rec iota i =
    if i = 0 then [] else
    iota (i - 1) @ [i]

let () = Scanf.scanf "%d %d\n" (fun k n ->
    let l = ref [] in
    for i = 1 to n do
        Scanf.scanf "%d %d\n" (fun x y -> l := (x, y) :: !l)
    done;
    print_endline (try let v = satisfy [] [] !l in
    String.concat " " (List.map (fun x -> if List.mem x v then "1" else "0") (iota k))
    with Exit -> "UNSOLVABLE") )

(das iota kWortspiel, das ich hoffe, Sie werden vergeben).

Matías Giovannini
quelle
Schön, die OCaml-Version zu sehen! Es macht den Start eines schönen Rosetta Stone für Funktionsprogramme. Wenn wir jetzt Scala- und F # -Versionen bekommen könnten ... - Was den Algorithmus betrifft - Ich habe dieses PDF erst gesehen, als Sie es hier erwähnt haben! Ich habe meine Implementierung anhand der Beschreibung der Wikipedia-Seite "Limited Backtracking" vorgenommen.
MtnViewMark