Memecahkan 2-SAT (kepuasan boolean)

16

Masalah umum SAT (boolean satisfiability) adalah NP-complete. Tapi 2-SAT , di mana setiap klausul hanya memiliki 2 variabel, adalah di P . Tulis solver untuk 2-SAT.

Memasukkan:

Contoh 2-SAT, dikodekan dalam CNF sebagai berikut. Baris pertama berisi V, jumlah variabel boolean dan N, jumlah klausa. Kemudian N baris mengikuti, masing-masing dengan 2 bilangan nol bukan mewakili literal klausa. Bilangan bulat positif mewakili variabel boolean yang diberikan dan bilangan bulat negatif mewakili negasi variabel.

Contoh 1

memasukkan

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

yang menyandikan rumus (x 1 atau x 2 ) dan (x 2 atau x 3 ) dan (x 3 atau x 4 ) dan (bukan x 1 atau tidak x 3 ) dan (bukan x 2 atau tidak x 4 ) .

Satu-satunya pengaturan dari 4 variabel yang membuat seluruh rumus menjadi benar adalah x 1 = salah, x 2 = benar, x 3 = benar, x 4 = salah , sehingga program Anda harus menampilkan satu baris

keluaran

0 1 1 0

mewakili nilai kebenaran dari variabel V (dalam urutan dari x 1 ke x V ). Jika ada beberapa solusi, Anda dapat menampilkan subset nonempty dari mereka, satu per baris. Jika tidak ada solusi, Anda harus mengeluarkan UNSOLVABLE.

Contoh 2

memasukkan

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

keluaran

UNSOLVABLE

Contoh 3

memasukkan

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

keluaran

0 1

Contoh 4

memasukkan

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

keluaran

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

(atau setiap subset kosong dari 3 baris)

Program Anda harus menangani semua N, V ​​<100 dalam waktu yang wajar. Coba contoh ini untuk memastikan program Anda dapat menangani contoh besar. Program terkecil menang.

Keith Randall
sumber
Anda menyebutkan bahwa 2-SAT ada di P, tetapi bukan berarti ini merupakan persyaratan bahwa solusi harus dijalankan dalam waktu polinomial ;-)
Timwi
@Timwi: Tidak, tetapi harus menangani V = 99 dalam waktu yang masuk akal ...
Keith Randall

Jawaban:

4

Haskell, 278 karakter

(∈)=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

Bukan kekerasan. Berjalan dalam waktu polinomial. Memecahkan masalah sulit (60 variabel, 99 klausa) dengan cepat:

> 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

Dan sebenarnya, sebagian besar waktu dihabiskan untuk menyusun kode!

File sumber lengkap, dengan kasus uji dan tes periksa cepat tersedia .

Tidak Digolkan:

-- | 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

Dalam versi golf, satisfydan formattelah dimasukkan ke dalam reduce, meskipun untuk menghindari lewat n, reducemengembalikan fungsi dari daftar variabel ( [1..n]) ke hasil string.


  • Sunting: (330 -> 323) membuat soperator, penanganan baris baru yang lebih baik
  • Sunting: (323 -> 313) elemen pertama dari daftar hasil yang malas lebih kecil dari operator korsleting kustom; berganti nama menjadi fungsi solver utama karena saya suka menggunakan sebagai operator!
  • Sunting: (313 -> 296) menyimpan klausa sebagai satu daftar, bukan daftar daftar; mengolahnya dua elemen sekaligus
  • Sunting: (296 -> 291) menggabungkan dua fungsi yang saling rekursif; lebih murah untuk inline jadi tes sekarang diganti namanya
  • Sunting: (291 -> 278) berformat keluaran berformat ke dalam hasil generasi
MtnViewMark
sumber
4

J, 119 103

echo'UNSOLVABLE'"_`(#&c)@.(*@+/)(3 :'*./+./"1(*>:*}.i)=y{~"1 0<:|}.i')"1 c=:#:i.2^{.,i=:0&".;._2(1!:1)3
  • Lewati semua kasus uji. Tidak ada runtime yang terlihat.
  • Paksaan. Lulus uji kasus di bawah ini, oh, N = 20 atau 30. Tidak yakin.
  • Diuji melalui skrip tes yang benar-benar mati otak (Dengan inspeksi visual)

Sunting: Dieliminasi (n#2)dan dengan demikian n=:, serta menghilangkan beberapa parens peringkat (terima kasih, isawdrones). Tacit-> eksplisit dan dyadic-> monadic, masing-masing menghilangkan beberapa karakter. }.}.untuk}., .

Edit: Whoops. Tidak hanya ini bukan solusi untuk N besar, tetapi i. 2^99x-> "kesalahan domain" untuk menambah penghinaan terhadap kebodohan.

Inilah versi asli dan penjelasan singkat yang tidak dikolongkan.

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 memotong input pada baris baru dan mem-parsing angka pada setiap baris (mengumpulkan hasil menjadi input).
  • n ditugaskan untuk n, matriks klausa ditugaskan untuk clauses(tidak perlu jumlah klausa)
  • casesadalah 0..2 n -1 dikonversi ke digit biner (semua kasus uji)
  • (Long tacit function)"(_,1)diterapkan untuk setiap kasus casesdengan semua clauses.
  • <:@|@[{"(0,1)] mendapatkan matriks dari operan klausa (dengan mengambil abs (nomor op) - 1 dan dereferencing dari kasus, yang merupakan array)
  • *@>:@*@[ mendapat array berbentuk klausa dari bit 'tidak tidak' (0 untuk tidak) melalui penyalahgunaan signum.
  • = menerapkan bit tidak ke operan.
  • [:*./[:+./"1berlaku +.(dan) melintasi baris matriks yang dihasilkan, dan *.(atau) melintasi hasil itu.
  • Semua hasil tersebut berakhir sebagai array biner dari 'jawaban' untuk setiap kasus.
  • *@+/ diterapkan pada hasil memberi 0 jika ada hasil dan 1 jika tidak ada.
  • ('UNSOLVABLE'"_) `(#&cases) @.(*@+/) results menjalankan fungsi konstan yang memberikan 'TIDAK DAPAT DIKECUALIKAN' jika 0, dan salinan dari setiap elemen 'solusi' dari kasus jika 1.
  • echo sulap-cetak hasilnya.
Jesse Millikan
sumber
Anda dapat menghapus parens di sekitar argumen pangkat. "(_,1)untuk "_ 1. #:akan bekerja tanpa argumen kiri.
isawdrones
@ isawdrones: Saya pikir respon tradisional akan menghancurkan semangat saya dengan menghasilkan jawaban setengah panjang. "Berteriak dan melompat", seperti yang akan dikatakan Kzin. Terima kasih, meskipun, menghilangkan 10-karakter aneh ... Saya mungkin mendapatkan di bawah 100 ketika saya kembali ke sana.
Jesse Millikan
+1 untuk penjelasan yang bagus dan terperinci, sangat menarik dibaca!
Timwi
Mungkin tidak akan menangani N = V = 99 dalam waktu yang wajar. Coba contoh besar yang baru saja saya tambahkan.
Keith Randall
3

K - 89

Metode yang sama dengan solusi J.

n:**c:.:'0:`;`0::[#b:t@&&/+|/''(0<'c)=/:(t:+2_vs!_2^n)@\:-1+_abs c:1_ c;5:b;"UNSOLVABLE"]
isawdrones
sumber
Bagus, saya tidak tahu ada implementasi K gratis.
Jesse Millikan
Mungkin tidak akan menangani N = V = 99 dalam waktu yang wajar. Coba contoh besar yang baru saja saya tambahkan.
Keith Randall
2

Ruby, 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'

Tapi itu lambat :(

Cukup mudah dibaca setelah diperluas:

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
sumber
Mungkin tidak akan menangani N = V = 99 dalam waktu yang wajar. Coba contoh besar yang baru saja saya tambahkan.
Keith Randall
1

Baterai OCaml +, 438 436 karakter

Membutuhkan Baterai OCaml Termasuk tingkat atas:

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))

Saya harus akui, ini adalah terjemahan langsung dari solusi Haskell. Dalam pembelaan saya, yang pada gilirannya adalah pengkodean langsung dari algoritma yang disajikan di sini [PDF], dengan saling satisfy-eliminate rekursi digulung menjadi satu fungsi. Versi kode yang tidak dikobarkan, dikurangi penggunaan Baterai, adalah:

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") )

(permainan iota kkata yang saya harap Anda akan memaafkan).

Matías Giovannini
sumber
Senang melihat versi OCaml! Itu membuat awal Rosetta Stone yang bagus untuk program fungsional. Sekarang jika kita bisa mendapatkan versi Scala dan F # ... - Adapun algoritma - saya tidak melihat PDF sampai Anda sebutkan di sini! Saya mendasarkan implementasi saya dari deskripsi halaman Wikipedia tentang "Pelacakan Terbatas".
MtnViewMark