#!/usr/bin/env python3
import itertools
import sys


def edge_map(n):
    idx = {}
    k = 1
    for i in range(n):
        for j in range(i + 1, n):
            idx[i, j] = k
            k += 1
    return idx


def lit_s(lit):
    if lit > 0:
        return f"1 x{lit}"
    return f"1 ~x{-lit}"


class Proof:
    def __init__(self, path, n_original):
        self.f = open(path, "w")
        self.next_id = n_original + 1
        self.f.write("pseudo-Boolean proof version 3.0\n")

    def constraint(self, lits, rhs):
        # Deduplicate while preserving order.
        seen = set()
        out = []
        for lit in lits:
            if lit not in seen:
                seen.add(lit)
                out.append(lit)
        if out:
            return " ".join(lit_s(x) for x in out) + f" >= {rhs}"
        return f">= {rhs}"

    def rup(self, lits, rhs=1):
        self.f.write("rup " + self.constraint(lits, rhs) + " ;\n")
        r = self.next_id
        self.next_id += 1
        return r

    def ia(self, lits, rhs=1):
        self.f.write("ia " + self.constraint(lits, rhs) + " ;\n")
        r = self.next_id
        self.next_id += 1
        return r

    def red(self, lits, rhs, witness):
        parts = []
        for var, lit in witness:
            if lit > 0:
                parts.append(f"x{var} -> x{lit}")
            elif lit < 0:
                parts.append(f"x{var} -> ~x{-lit}")
            else:
                parts.append(f"x{var} -> 0")
        self.f.write("red " + self.constraint(lits, rhs) + " : " + " ".join(parts) + " ;\n")
        r = self.next_id
        self.next_id += 1
        return r

    def pol_sum_div(self, ids, div=1):
        assert ids
        expr = [str(ids[0])]
        for x in ids[1:]:
            expr.append(str(x))
            expr.append("+")
        if div != 1:
            expr.append(str(div))
            expr.append("d")
        expr.append("s")
        self.f.write("pol " + " ".join(expr) + " ;\n")
        r = self.next_id
        self.next_id += 1
        return r

    def finish_unsat(self, cid):
        self.f.write("output NONE ;\n")
        self.f.write(f"conclusion UNSAT : {cid} ;\n")
        self.f.write("end pseudo-Boolean proof ;\n")
        self.f.close()


def cnf_ramsey(path, n, a, b):
    idx = edge_map(n)
    clauses = []
    for verts in itertools.combinations(range(n), a):
        lits = []
        for i, j in itertools.combinations(verts, 2):
            lits.append(idx[min(i, j), max(i, j)])
        clauses.append(lits)
    for verts in itertools.combinations(range(n), b):
        lits = []
        for i, j in itertools.combinations(verts, 2):
            lits.append(-idx[min(i, j), max(i, j)])
        clauses.append(lits)
    with open(path, "w") as f:
        f.write(f"p cnf {n*(n-1)//2} {len(clauses)}\n")
        for c in clauses:
            f.write(" ".join(map(str, c)) + " 0\n")
    return len(clauses)


def lift_at_least(proof, elems, q, base_ids):
    """From each q-subset requiring >=1 of elems, derive full >= len-q+1."""
    prev = {tuple(s): base_ids[tuple(s)] for s in itertools.combinations(elems, q)}
    for k in range(q + 1, len(elems) + 1):
        cur = {}
        for s in itertools.combinations(elems, k):
            ids = []
            ss = set(s)
            for drop in s:
                ids.append(prev[tuple(x for x in s if x != drop)])
            cur[tuple(s)] = proof.pol_sum_div(ids, k - 1)
        prev = cur
    return prev[tuple(elems)]


def prove_r33(proof, vertices, context, idx, red_true=True):
    """Prove context >= 1 for no red K3/no blue K3 on 6 vertices."""
    def rlit(a, b):
        e = idx[min(a, b), max(a, b)]
        return e if red_true else -e

    def blit(a, b):
        return -rlit(a, b)

    p = vertices[0]
    neigh = list(vertices[1:])
    red_edges = [rlit(p, u) for u in neigh]
    blue_edges = [blit(p, u) for u in neigh]

    # No three red neighbors: context OR one incident edge is blue.
    base_blue = {}
    for comb in itertools.combinations(range(len(neigh)), 3):
        lits = list(context) + [blue_edges[i] for i in comb]
        base_blue[tuple(blue_edges[i] for i in comb)] = proof.rup(lits)
    blue_full = lift_at_least(proof, blue_edges, 3, base_blue)

    # No three blue neighbors: context OR one incident edge is red.
    base_red = {}
    for comb in itertools.combinations(range(len(neigh)), 3):
        lits = list(context) + [red_edges[i] for i in comb]
        base_red[tuple(red_edges[i] for i in comb)] = proof.rup(lits)
    red_full = lift_at_least(proof, red_edges, 3, base_red)

    return proof.pol_sum_div([blue_full, red_full])


def prove_r34(proof, vertices, context, idx, red_true=True):
    """Prove context >= 1 for no red K3/no blue K4 on 9 vertices."""
    def rlit(a, b):
        e = idx[min(a, b), max(a, b)]
        return e if red_true else -e

    def blit(a, b):
        return -rlit(a, b)

    lower_red = []
    upper_blue = []
    for v in vertices:
        neigh = [u for u in vertices if u != v]
        red_edges = [rlit(v, u) for u in neigh]
        blue_edges = [blit(v, u) for u in neigh]

        # Red degree <= 3: every 4 red-neighbor choice is impossible.
        base = {}
        for comb in itertools.combinations(range(len(neigh)), 4):
            lits = list(context) + [blue_edges[i] for i in comb]
            key = tuple(blue_edges[i] for i in comb)
            base[key] = proof.rup(lits)
        upper_blue.append(lift_at_least(proof, blue_edges, 4, base))

        # Red degree >= 3: every 6 blue-neighbor choice gives R(3,3,6).
        base = {}
        for comb in itertools.combinations(range(len(neigh)), 6):
            sub = [neigh[i] for i in comb]
            lits = list(context) + [red_edges[i] for i in comb]
            key = tuple(red_edges[i] for i in comb)
            base[key] = prove_r33(proof, sub, lits, idx, red_true)
        lower_red.append(lift_at_least(proof, red_edges, 6, base))

    ge14 = proof.pol_sum_div(lower_red, 2)
    ge23blue = proof.pol_sum_div(upper_blue, 2)
    return proof.pol_sum_div([ge14, ge23blue])


def perm_witness(n, idx, perm, complement=False, only_changed=True):
    out = []
    for a in range(n):
        for b in range(a + 1, n):
            v = idx[a, b]
            pa, pb = perm[a], perm[b]
            if pa > pb:
                pa, pb = pb, pa
            image = idx[pa, pb]
            lit = -image if complement else image
            if (not only_changed) or lit != v:
                out.append((v, lit))
    return out


def prove_r44_sbp(proof, n, idx):
    assert n >= 18
    # Work on vertex 0 and vertices 1..17.  Add a lexicographic symmetry
    # break for the colors of edges (0,i): x01 >= x02 >= ... >= x0,17.
    e = [idx[0, i] for i in range(1, 18)]
    for i in range(1, 17):
        perm = list(range(n))
        # If the next adjacent ordering constraint is violated, rotate the
        # later red edge to the front of the already ordered prefix.
        perm[1] = i + 1
        for j in range(2, i + 2):
            perm[j] = j - 1
        proof.red([idx[0, i], -idx[0, i + 1]], 1, perm_witness(n, idx, perm))

    # With the chain installed, color-complement plus reversal of vertices
    # 1..17 maps any sorted assignment with fewer than nine true incident
    # edges to one with at least nine true incident edges.
    perm = list(range(n))
    for i in range(1, 18):
        perm[i] = 18 - i
    proof.red([idx[0, 9]], 1, perm_witness(n, idx, perm, complement=True, only_changed=False))

    # Chain clauses and x09 propagate x01..x09.  The R(3,4)=9 subproof under
    # those nine red incident edges derives their negated disjunction.
    context = [-idx[0, i] for i in range(1, 10)]
    lemma = prove_r34(proof, list(range(1, 10)), context, idx, True)
    return proof.rup([], 1)


def main():
    n = int(sys.argv[1])
    a = int(sys.argv[2])
    b = int(sys.argv[3])
    cnf = sys.argv[4]
    pbp = sys.argv[5]
    nclauses = cnf_ramsey(cnf, n, a, b)
    idx = edge_map(n)
    proof = Proof(pbp, nclauses)
    if (a, b, n) == (3, 3, 6):
        cid = prove_r33(proof, list(range(6)), [], idx, True)
    elif (a, b, n) == (3, 4, 9):
        cid = prove_r34(proof, list(range(9)), [], idx, False)
    elif (a, b, n) == (4, 4, 18):
        cid = prove_r44_sbp(proof, n, idx)
    else:
        raise SystemExit("unsupported")
    proof.finish_unsat(cid)


if __name__ == "__main__":
    main()
