แก้ 2-SAT (ความน่าเชื่อถือแบบบูล)


16

ปัญหาSAT (บูลีนความพึงพอใจ) โดยทั่วไปคือ NP-complete แต่2-SAT , ข้อที่แต่ละคนมีเพียง 2 ตัวแปรอยู่ในP เขียนตัวแก้สำหรับ 2-SAT

การป้อนข้อมูล:

อินสแตนซ์ 2-SAT เข้ารหัสในCNFดังนี้ บรรทัดแรกมี V, จำนวนตัวแปรบูลีนและ N, จำนวนส่วนคำสั่ง จากนั้นบรรทัด N จะตามมาแต่ละบรรทัดจะมีจำนวนเต็มไม่ใช่ศูนย์ 2 ตัวซึ่งแทนค่าตัวอักษร จำนวนเต็มบวกแสดงถึงตัวแปรบูลีนที่กำหนดและจำนวนเต็มลบแสดงถึงการปฏิเสธของตัวแปร

ตัวอย่างที่ 1

อินพุต

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

ซึ่ง encodes สูตร(x 1หรือ x 2 ) และ (x 2หรือ x 3 ) และ (x 3หรือ x 4 ) และ (ไม่ x 1หรือไม่ x 3 ) และ (ไม่ x 2หรือไม่ x 4 )

การตั้งค่าตัวแปร 4 ตัวเท่านั้นที่ทำให้สูตรทั้งหมดเป็นจริงคือx 1 = false, x 2 = true, x 3 = true, x 4 = falseดังนั้นโปรแกรมของคุณควรแสดงบรรทัดเดียว

เอาท์พุต

0 1 1 0

แสดงถึงค่าความจริงของตัวแปร V (ตามลำดับจากx 1ถึงx V ) หากมีหลายวิธีคุณอาจส่งออกชุดย่อยที่ไม่ว่างของพวกเขาหนึ่งรายการต่อบรรทัด UNSOLVABLEหากมีการแก้ปัญหาไม่ได้คุณต้องเอาท์พุท

ตัวอย่างที่ 2

อินพุต

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

เอาท์พุต

UNSOLVABLE

ตัวอย่างที่ 3

อินพุต

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

เอาท์พุต

0 1

ตัวอย่างที่ 4

อินพุต

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

เอาท์พุต

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

(หรือเซตย่อยที่ไม่มีข้อยกเว้นของ 3 บรรทัดเหล่านั้น)

โปรแกรมของคุณจะต้องจัดการกับ N, V ​​<100 ในเวลาที่เหมาะสม ลองตัวอย่างนี้เพื่อให้แน่ใจว่าโปรแกรมของคุณสามารถจัดการกับอินสแตนซ์ใหญ่ โปรแกรมที่เล็กที่สุดชนะ


คุณพูดถึงว่า 2-SAT อยู่ใน P แต่ไม่ว่ามันเป็นความต้องการว่าการแก้ปัญหาจะต้องทำงานในเวลาพหุนาม ;-)
Timwi

@Timwi: ไม่มี แต่ก็มีการจับ V = 99 ในเวลาที่เหมาะสม ...
คี ธ แรนดัล

คำตอบ:


4

Haskell, 278 ตัวอักษร

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

ไม่ดุร้าย ทำงานในเวลาพหุนาม แก้ไขปัญหาที่ยาก (60 ตัวแปร 99 ข้อ) ได้อย่างรวดเร็ว:

> 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

และจริงๆแล้วเวลาส่วนใหญ่นั้นใช้ในการรวบรวมรหัส!

แฟ้มแหล่งที่มาเต็มกับกรณีทดสอบและการทดสอบอย่างรวดเร็วตรวจสอบที่มีอยู่

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

ในเวอร์ชัน golf'd satisfyและformatถูกนำไปกลิ้งreduceแม้ว่าเพื่อหลีกเลี่ยงการส่งผ่านnให้reduceส่งคืนฟังก์ชันจากรายการของตัวแปร ( [1..n]) ไปยังผลลัพธ์ของสตริง


  • แก้ไข: (330 -> 323) สร้างsตัวดำเนินการจัดการกับ newline ได้ดีขึ้น
  • แก้ไข: (323 -> 313) องค์ประกอบแรกจากรายการผลลัพธ์ที่ขี้เกียจมีขนาดเล็กกว่าตัวดำเนินการลัดวงจรแบบกำหนดเอง เปลี่ยนชื่อฟังก์ชั่นแก้ปัญหาหลักเพราะฉันชอบใช้เป็นผู้ประกอบการ!
  • แก้ไข: (313 -> 296) เก็บคำสั่งเป็นรายการเดียวไม่ใช่รายการ ประมวลผลสององค์ประกอบพร้อมกัน
  • แก้ไข: (296 -> 291) ผสานทั้งสองฟังก์ชั่นวนซ้ำซึ่งกันและกัน มันราคาถูกกว่าแบบอินไลน์ดังนั้นตอนนี้เปลี่ยนชื่อทดสอบ
  • แก้ไข: (291 -> 278) การจัดรูปแบบเอาต์พุตแบบอินไลน์เข้าสู่การสร้างผลลัพธ์

4

J, 119 103

echo'UNSOLVABLE'"_`(#&c)@.(*@+/)(3 :'*./+./"1(*>:*}.i)=y{~"1 0<:|}.i')"1 c=:#:i.2^{.,i=:0&".;._2(1!:1)3
  • ผ่านทุกกรณีทดสอบ ไม่มีรันไทม์ที่เห็นได้ชัดเจน
  • กำลังดุร้าย. ผ่านกรณีทดสอบด้านล่างโอ้ N = 20 หรือ 30 ไม่แน่ใจ
  • ผ่านการทดสอบโดยสคริปต์ทดสอบสมองตายอย่างสมบูรณ์ (โดยการตรวจสอบด้วยภาพ)

แก้ไข:ตัดออก(n#2)และทำให้n=:ตลอดจนขจัด parens ยศบางคน (ขอบคุณ isawdrones) Tacit-> ชัดแจ้งและ dyadic-> monadic กำจัดอักขระเพิ่มเติมสองสามตัว ไปยัง}.}.}.,

แก้ไข:อ๊ะ ไม่เพียง แต่เป็นวิธีแก้ปัญหาสำหรับ N ขนาดใหญ่ แต่i. 2^99x-> "ข้อผิดพลาดโดเมน" เพื่อเพิ่มการดูถูกความโง่เขลา

ต่อไปนี้เป็นเวอร์ชั่นดั้งเดิมที่ไม่มีคำอธิบายและคำอธิบายสั้น ๆ

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 ตัดอินพุตในบรรทัดใหม่และแยกวิเคราะห์ตัวเลขในแต่ละบรรทัด (รวบรวมผลลัพธ์ลงในอินพุต)
  • n ได้รับมอบหมายให้n, เมทริกซ์ข้อได้รับมอบหมายให้clauses(ไม่จำเป็นต้องนับข้อ)
  • casesคือ 0..2 n -1 แปลงเป็นเลขฐานสอง (กรณีทดสอบทั้งหมด)
  • (Long tacit function)"(_,1)ถูกนำไปใช้ในแต่ละกรณีที่มีทั้งหมดของcasesclauses
  • <:@|@[{"(0,1)] ได้รับเมทริกซ์ของตัวถูกดำเนินการของข้อ (โดยการ abs (หมายเลข op) - 1 และ dereferencing จากกรณีซึ่งเป็นอาร์เรย์)
  • *@>:@*@[ ได้รับอาร์เรย์รูปประโยคของบิต 'ไม่ไม่' (0 ไม่) ผ่านการละเมิดของ signum
  • = ใช้ไม่บิตกับตัวถูกดำเนินการ
  • [:*./[:+./"1ใช้+.(และ) ข้ามแถวของเมทริกซ์ที่ได้และและ*.(หรือ) ข้ามผลลัพธ์นั้น
  • ผลลัพธ์ทั้งหมดเหล่านั้นจบลงด้วยการเป็น 'อาร์เรย์' ของคำตอบสำหรับแต่ละกรณี
  • *@+/ นำไปใช้กับผลลัพธ์จะให้ 0 หากมีผลลัพธ์และ 1 หากไม่มี
  • ('UNSOLVABLE'"_) `(#&cases) @.(*@+/) results เรียกใช้ฟังก์ชั่นคงที่โดยให้ 'UNSOLVABLE' ถ้าเป็น 0 และคัดลอกองค์ประกอบ 'solution' ของแต่ละกรณีถ้า 1
  • echo magic-print ผลลัพธ์

คุณสามารถลบ parens รอบ ๆ อาร์กิวเมนต์อันดับ ไปยัง"(_,1) จะทำงานโดยไม่มีอาร์กิวเมนต์ซ้าย "_ 1#:
isawdrones

@ isawdrones: ฉันคิดว่าการตอบสนองแบบดั้งเดิมจะบดขยี้จิตวิญญาณของฉันโดยการผลิตครึ่งคำตอบนาน "Scream and leap" ตามที่ Kzin พูด ขอบคุณที่กำจัดตัวละครแปลก ๆ 10 ตัว ... ฉันอาจอายุต่ำกว่า 100 เมื่อกลับไปที่เดิม
Jesse Millikan

+1 สำหรับคำอธิบายที่ดีและมีรายละเอียดอ่านที่น่าสนใจมาก!
Timwi

อาจจะไม่จัดการกับ N = V = 99 ในเวลาที่เหมาะสม ลองตัวอย่างใหญ่ที่ฉันเพิ่งเพิ่มเข้าไป
Keith Randall

3

K - 89

วิธีการเดียวกับวิธีการแก้ปัญหา J

n:**c:.:'0:`;`0::[#b:t@&&/+|/''(0<'c)=/:(t:+2_vs!_2^n)@\:-1+_abs c:1_ c;5:b;"UNSOLVABLE"]

ดีฉันไม่รู้ว่ามีการนำ K มาใช้ฟรี
Jesse Millikan

อาจจะไม่จัดการกับ N = V = 99 ในเวลาที่เหมาะสม ลองตัวอย่างใหญ่ที่ฉันเพิ่งเพิ่มเข้าไป
Keith Randall

2

ทับทิม, 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'

แต่มันช้า :(

สวยอ่านได้เมื่อขยาย:

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'

อาจจะไม่จัดการกับ N = V = 99 ในเวลาที่เหมาะสม ลองตัวอย่างใหญ่ที่ฉันเพิ่งเพิ่มเข้าไป
Keith Randall

1

OCaml + แบตเตอรี่438 436 ตัวอักษร

ต้องใช้แบตเตอรี่ OCaml ที่รวมอยู่ในระดับบนสุด:

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

ฉันต้องสารภาพนี่คือคำแปลโดยตรงของโซลูชัน Haskell ในการป้องกันของฉันที่ในการเปิดเป็นโดยตรงการเข้ารหัสของขั้นตอนวิธีนำเสนอที่นี่ [PDF] ด้วยการร่วมกันsatisfy- eliminateการเรียกซ้ำรีดเป็นฟังก์ชั่นเดียว โค้ดเวอร์ชันที่ไม่ทำให้ยุ่งเหยิงลบด้วยการใช้แบตเตอรี่คือ:

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

( iota kปุนฉันหวังว่าคุณจะให้อภัย)


ดีใจที่ได้เห็นเวอร์ชั่น OCaml! มันทำให้การเริ่มต้นของ Rosetta Stone ที่ดีสำหรับโปรแกรมการทำงาน ทีนี้ถ้าเราได้รุ่น Scala และ F # ... - สำหรับอัลกอริทึม - ฉันไม่เห็น PDF นั้นจนกว่าคุณจะพูดถึงที่นี่! ฉันใช้งานตามคำอธิบายของหน้าวิกิพีเดียว่า "Backtracking แบบ จำกัด "
MtnViewMark
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.