#include <algorithm>
#include <cmath>
#include <cstdint>
#include <fstream>
#include <functional>
#include <iostream>
#include <numeric>
#include <set>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>

using Clause = std::vector<int>;

struct CNF {
    int nvars = 0;
    int declared_clauses = 0;
    std::vector<Clause> clauses;
};

struct Shape {
    int n = 0;      // board side
    int m = 0;      // number of cells/values
    bool cell_alo = false;
    bool direct_adjacency = false;
    bool direct_adjacency_transposed = false;
};

static uint64_t bit_for(int idx) {
    return 1ULL << idx;
}

static uint64_t all_mask(int m) {
    return m == 64 ? ~0ULL : ((1ULL << m) - 1ULL);
}

static int var_of(int cell, int value, int m) {
    return cell * m + value;
}

static int var_of_oriented(int cell, int value, int m, bool transposed) {
    return transposed ? ((value - 1) * m + cell + 1) : var_of(cell, value, m);
}

static void decode_primary_var(int var, int m, bool transposed, int& cell, int& value) {
    if (transposed) {
        value = (var - 1) / m + 1;
        cell = (var - 1) % m;
    } else {
        cell = (var - 1) / m;
        value = (var - 1) % m + 1;
    }
}

static bool parse_dimacs(const std::string& path, CNF& cnf) {
    std::ifstream in(path);
    if (!in) return false;
    std::string line;
    Clause cur;
    while (std::getline(in, line)) {
        if (line.empty() || line[0] == 'c') continue;
        if (line[0] == 'p') {
            std::istringstream iss(line);
            std::string p, fmt;
            iss >> p >> fmt >> cnf.nvars >> cnf.declared_clauses;
            continue;
        }
        std::istringstream iss(line);
        int lit;
        while (iss >> lit) {
            if (lit == 0) {
                if (!cur.empty()) {
                    cnf.clauses.push_back(cur);
                    cur.clear();
                }
            } else {
                cur.push_back(lit);
            }
        }
    }
    if (!cur.empty()) cnf.clauses.push_back(cur);
    if (cnf.declared_clauses == 0) cnf.declared_clauses = static_cast<int>(cnf.clauses.size());
    return cnf.nvars > 0 && !cnf.clauses.empty();
}

static std::vector<std::vector<int>> king_neighbors(int n) {
    int m = n * n;
    std::vector<std::vector<int>> nb(m);
    for (int cell = 0; cell < m; ++cell) {
        int r = cell / n, c = cell % n;
        for (int dr = -1; dr <= 1; ++dr) {
            for (int dc = -1; dc <= 1; ++dc) {
                if (dr == 0 && dc == 0) continue;
                int rr = r + dr, cc = c + dc;
                if (0 <= rr && rr < n && 0 <= cc && cc < n) nb[cell].push_back(rr * n + cc);
            }
        }
    }
    return nb;
}

static Shape detect_shape(const CNF& cnf) {
    Shape s;
    std::vector<Clause> sorted_clauses;
    sorted_clauses.reserve(cnf.clauses.size());
    for (auto cl : cnf.clauses) {
        std::sort(cl.begin(), cl.end());
        sorted_clauses.push_back(std::move(cl));
    }
    std::sort(sorted_clauses.begin(), sorted_clauses.end());

    for (int cand_n = 2; cand_n <= 20; ++cand_n) {
        int m = cand_n * cand_n;
        if (1LL * m * m > cnf.nvars) continue;
        std::vector<char> has_cell_alo(m, 0), has_strided_alo(m, 0);
        std::vector<char> unit_cell(m, 0), unit_strided_cell(m, 0);
        for (const auto& cl : cnf.clauses) {
            if (cl.size() == 1 && cl[0] > 0 && cl[0] <= m * m) {
                int z = cl[0] - 1;
                unit_cell[z / m] = 1;
                unit_strided_cell[z % m] = 1;
            }
        }
        for (const auto& cl : cnf.clauses) {
            if (static_cast<int>(cl.size()) != m) continue;
            bool all_pos = true;
            int mn = cnf.nvars + 1, mx = 0;
            long long sum = 0;
            std::vector<int> lits;
            lits.reserve(m);
            for (int lit : cl) {
                if (lit <= 0) {
                    all_pos = false;
                    break;
                }
                lits.push_back(lit);
                mn = std::min(mn, lit);
                mx = std::max(mx, lit);
                sum += lit;
            }
            if (!all_pos) continue;
            if (mx - mn + 1 == m && (mn - 1) % m == 0) {
                int cell = (mn - 1) / m;
                if (cell >= 0 && cell < m && mx == cell * m + m) {
                    long long want = 1LL * (mn + mx) * m / 2;
                    if (sum == want) has_cell_alo[cell] = 1;
                }
            }
            int offset = (lits[0] - 1) % m;
            bool strided = true;
            std::vector<char> seen(m, 0);
            for (int lit : lits) {
                int z = lit - 1;
                if (z < 0 || z >= m * m || z % m != offset) {
                    strided = false;
                    break;
                }
                int q = z / m;
                if (seen[q]) {
                    strided = false;
                    break;
                }
                seen[q] = 1;
            }
            if (strided && std::all_of(seen.begin(), seen.end(), [](char x) { return x; })) {
                has_strided_alo[offset] = 1;
            }
        }
        int normal_count = 0, strided_count = 0;
        bool ok = true, strided_ok = true;
        for (int cell = 0; cell < m; ++cell) {
            if (has_cell_alo[cell]) ++normal_count;
            if (has_strided_alo[cell]) ++strided_count;
            ok = ok && (has_cell_alo[cell] || unit_cell[cell]);
            strided_ok = strided_ok && (has_strided_alo[cell] || unit_strided_cell[cell]);
        }
        ok = ok || normal_count >= m - 1;
        strided_ok = strided_ok || strided_count >= m - 1;
        if (ok || strided_ok) {
            s.n = cand_n;
            s.m = m;
            s.cell_alo = true;
        }
    }
    if (!s.cell_alo) return s;
    int n = s.n;
    int m = s.m;

    auto contains_clause = [&](Clause cl) {
        std::sort(cl.begin(), cl.end());
        return std::binary_search(sorted_clauses.begin(), sorted_clauses.end(), cl);
    };

    auto nb = king_neighbors(n);
    auto check_direct = [&](bool transposed) {
        bool direct = true;
        for (int value = 1; value < m && direct; ++value) {
            for (int cell = 0; cell < m && direct; ++cell) {
                Clause fwd;
                fwd.push_back(-var_of_oriented(cell, value, m, transposed));
                for (int to : nb[cell]) fwd.push_back(var_of_oriented(to, value + 1, m, transposed));
                if (!contains_clause(fwd)) {
                    direct = false;
                    break;
                }
                Clause bwd;
                bwd.push_back(-var_of_oriented(cell, value + 1, m, transposed));
                for (int to : nb[cell]) bwd.push_back(var_of_oriented(to, value, m, transposed));
                if (!contains_clause(bwd)) direct = false;
            }
        }
        return direct;
    };

    s.direct_adjacency = check_direct(false);
    s.direct_adjacency_transposed = check_direct(true);
    return s;
}

struct ACResult {
    bool unsat = false;
    std::string reason;
};

static std::vector<int> positive_primary_units(const CNF& cnf, int m) {
    std::vector<int> units;
    int primary_limit = m * m;
    for (const auto& cl : cnf.clauses) {
        if (cl.size() == 1 && cl[0] > 0 && cl[0] <= primary_limit) units.push_back(cl[0]);
    }
    return units;
}

static std::vector<int> primary_units(const CNF& cnf, int m) {
    std::vector<int> units;
    int primary_limit = m * m;
    for (const auto& cl : cnf.clauses) {
        if (cl.size() == 1 && std::abs(cl[0]) <= primary_limit) units.push_back(cl[0]);
    }
    return units;
}

class ProofWriter {
public:
    ProofWriter(std::ostream& out, int initial_id) : out_(out), cur_id_(initial_id) {
        out_ << "pseudo-Boolean proof version 3.0\n";
    }

    long long add_rup(const std::string& body) {
        out_ << "rup " << body << " ;\n";
        return ++cur_id_;
    }

    long long add_pol(const std::string& body) {
        out_ << "pol " << body << " ;\n";
        return ++cur_id_;
    }

    long long current_id() const { return cur_id_; }

    void finish_unsat() {
        out_ << "output NONE ;\n";
        out_ << "conclusion UNSAT : -1 ;\n";
        out_ << "end pseudo-Boolean proof ;\n";
    }

private:
    std::ostream& out_;
    long long cur_id_;
};

static std::string clause_to_pb(const Clause& cl) {
    if (cl.empty()) return ">= 1";
    std::ostringstream oss;
    for (size_t i = 0; i < cl.size(); ++i) {
        if (i) oss << ' ';
        int lit = cl[i];
        if (lit > 0) {
            oss << "1 x" << lit;
        } else {
            oss << "1 ~x" << -lit;
        }
    }
    oss << " >= 1";
    return oss.str();
}

static void derive_pb_scaffolding(int m, ProofWriter& proof, bool transposed = false) {
    std::vector<long long> cell_amo;
    cell_amo.reserve(m);

    for (int cell = 0; cell < m; ++cell) {
        std::vector<std::vector<long long>> pair(m + 1, std::vector<long long>(m + 1, 0));
        for (int j = 2; j <= m; ++j) {
            for (int i = 1; i < j; ++i) {
                std::ostringstream oss;
                oss << "1 ~x" << var_of_oriented(cell, i, m, transposed)
                    << " 1 ~x" << var_of_oriented(cell, j, m, transposed) << " >= 1";
                pair[i][j] = proof.add_rup(oss.str());
            }
        }
        long long cprev = pair[1][2];
        for (int k = 3; k <= m; ++k) {
            std::ostringstream b;
            b << pair[1][k];
            for (int i = 2; i < k; ++i) b << ' ' << pair[i][k] << " +";
            long long bid = proof.add_pol(b.str());

            std::ostringstream c;
            c << bid << ' ' << cprev << ' ' << (k - 2) << " * + " << (k - 1) << " d";
            cprev = proof.add_pol(c.str());
        }
        cell_amo.push_back(cprev);
    }

    std::vector<long long> occurrence;
    occurrence.reserve(m);
    for (int value = 1; value <= m; ++value) {
        std::ostringstream oss;
        for (int cell = 0; cell < m; ++cell) {
            if (cell) oss << ' ';
            oss << "1 x" << var_of_oriented(cell, value, m, transposed);
        }
        oss << " >= 1";
        occurrence.push_back(proof.add_rup(oss.str()));
    }

    std::ostringstream total;
    total << cell_amo[0];
    for (int i = 1; i < m; ++i) total << ' ' << cell_amo[i] << " +";
    long long total_id = proof.add_pol(total.str());

    for (int value = 1; value <= m; ++value) {
        std::ostringstream oss;
        oss << total_id;
        for (int other = 1; other <= m; ++other) {
            if (other == value) continue;
            oss << ' ' << occurrence[other - 1] << " +";
        }
        proof.add_pol(oss.str());
    }
}

static void emit_value_pairwise_amo(int m, ProofWriter& proof, bool transposed = false) {
    for (int value = 1; value <= m; ++value) {
        for (int c2 = 1; c2 < m; ++c2) {
            for (int c1 = 0; c1 < c2; ++c1) {
                Clause cl = {
                    -var_of_oriented(c1, value, m, transposed),
                    -var_of_oriented(c2, value, m, transposed)
                };
                proof.add_rup(clause_to_pb(cl));
            }
        }
    }
}

static ACResult run_arc_consistency(const CNF& cnf, const Shape& shape, ProofWriter* proof, bool transposed = false) {
    int n = shape.n, m = shape.m;
    auto nb = king_neighbors(n);
    std::vector<std::vector<char>> dom(m + 1, std::vector<char>(m, 1));
    std::vector<int> units = primary_units(cnf, m);

    std::vector<char> known_pos(m * m + 1, 0), known_neg(m * m + 1, 0);
    for (int lit : units) {
        if (lit > 0) known_pos[lit] = 1;
        else known_neg[-lit] = 1;
    }

    for (int lit : units) {
        if (lit > 0) continue;
        int var = -lit;
        int cell, value;
        decode_primary_var(var, m, transposed, cell, value);
        dom[value][cell] = 0;
    }

    for (int lit : units) {
        if (lit < 0) continue;
        int cell, value;
        decode_primary_var(lit, m, transposed, cell, value);
        if (!dom[value][cell]) {
            for (int c = 0; c < m; ++c) dom[value][c] = 0;
        } else {
            for (int c = 0; c < m; ++c) dom[value][c] = (c == cell);
        }
    }

    auto emit_pos = [&](int lit) {
        if (!proof || known_pos[lit]) return;
        known_pos[lit] = 1;
        std::ostringstream oss;
        oss << "1 x" << lit << " >= 1";
        proof->add_rup(oss.str());
    };
    auto emit_neg = [&](int lit) {
        if (!proof || known_neg[lit]) return;
        known_neg[lit] = 1;
        std::ostringstream oss;
        oss << "1 ~x" << lit << " >= 1";
        proof->add_rup(oss.str());
    };

    while (true) {
        bool changed = false;
        std::vector<int> singleton_value_for_cell(m, 0);
        std::vector<int> domain_size(m + 1, 0);
        std::vector<int> singleton_cell(m + 1, -1);

        for (int value = 1; value <= m; ++value) {
            for (int cell = 0; cell < m; ++cell) {
                if (!dom[value][cell]) continue;
                ++domain_size[value];
                singleton_cell[value] = cell;
            }
            if (domain_size[value] == 0) return {true, "empty value"};
            if (domain_size[value] == 1) {
                int cell = singleton_cell[value];
                emit_pos(var_of_oriented(cell, value, m, transposed));
                if (singleton_value_for_cell[cell] != 0) return {true, "duplicate cell"};
                singleton_value_for_cell[cell] = value;
            }
        }

        std::vector<char> occupied(m, 0);
        for (int cell = 0; cell < m; ++cell) {
            if (singleton_value_for_cell[cell]) occupied[cell] = 1;
        }

        for (int value = 1; value <= m; ++value) {
            if (domain_size[value] == 1) continue;
            for (int cell = 0; cell < m; ++cell) {
                if (!dom[value][cell] || !occupied[cell]) continue;
                emit_neg(var_of_oriented(cell, value, m, transposed));
                dom[value][cell] = 0;
                changed = true;
            }
        }

        for (int value = 1; value <= m; ++value) {
            for (int cell = 0; cell < m; ++cell) {
                if (!dom[value][cell]) continue;
                bool ok = true;
                if (value > 1) {
                    bool any = false;
                    for (int to : nb[cell]) {
                        if (dom[value - 1][to]) {
                            any = true;
                            break;
                        }
                    }
                    if (!any) ok = false;
                }
                if (ok && value < m) {
                    bool any = false;
                    for (int to : nb[cell]) {
                        if (dom[value + 1][to]) {
                            any = true;
                            break;
                        }
                    }
                    if (!any) ok = false;
                }
                if (!ok) {
                    emit_neg(var_of_oriented(cell, value, m, transposed));
                    dom[value][cell] = 0;
                    changed = true;
                }
            }
        }

        if (!changed) break;
    }
    return {false, "fixed point"};
}

static std::vector<Clause> build_primary_dpll_clauses(const CNF& cnf, const Shape& shape, bool transposed = false) {
    int m = shape.m;
    int primary_limit = m * m;
    std::vector<Clause> clauses;

    for (const auto& cl : cnf.clauses) {
        bool primary_only = true;
        for (int lit : cl) {
            if (std::abs(lit) > primary_limit) {
                primary_only = false;
                break;
            }
        }
        if (primary_only) clauses.push_back(cl);
    }

    for (int cell = 0; cell < m; ++cell) {
        for (int v2 = 2; v2 <= m; ++v2) {
            for (int v1 = 1; v1 < v2; ++v1) {
                clauses.push_back({
                    -var_of_oriented(cell, v1, m, transposed),
                    -var_of_oriented(cell, v2, m, transposed)
                });
            }
        }
    }

    for (int value = 1; value <= m; ++value) {
        Clause occurrence;
        occurrence.reserve(m);
        for (int cell = 0; cell < m; ++cell) occurrence.push_back(var_of_oriented(cell, value, m, transposed));
        clauses.push_back(std::move(occurrence));

        for (int c2 = 1; c2 < m; ++c2) {
            for (int c1 = 0; c1 < c2; ++c1) {
                clauses.push_back({
                    -var_of_oriented(c1, value, m, transposed),
                    -var_of_oriented(c2, value, m, transposed)
                });
            }
        }
    }

    return clauses;
}

class PrimaryProofDPLL {
public:
    enum class Result { Unsat, Sat, Limit };

    PrimaryProofDPLL(int m, bool transposed, std::vector<Clause> clauses, ProofWriter& proof, long long node_limit)
        : m_(m),
          primary_vars_(m * m),
          transposed_(transposed),
          clauses_(std::move(clauses)),
          proof_(proof),
          node_limit_(node_limit),
          assign_(primary_vars_ + 1, 0) {}

    Result prove() { return dfs(); }

private:
    int m_;
    int primary_vars_;
    bool transposed_;
    std::vector<Clause> clauses_;
    ProofWriter& proof_;
    long long node_limit_;
    long long nodes_ = 0;
    std::vector<int8_t> assign_;
    std::vector<int> trail_;
    std::vector<int> assumptions_;

    int lit_value(int lit) const {
        int8_t v = assign_[std::abs(lit)];
        if (v == 0) return 0;
        return ((lit > 0) == (v > 0)) ? 1 : -1;
    }

    bool set_lit(int lit) {
        int var = std::abs(lit);
        int8_t val = lit > 0 ? 1 : -1;
        if (assign_[var] == val) return true;
        if (assign_[var] == -val) return false;
        assign_[var] = val;
        trail_.push_back(var);
        return true;
    }

    void undo(size_t mark) {
        while (trail_.size() > mark) {
            assign_[trail_.back()] = 0;
            trail_.pop_back();
        }
    }

    bool propagate() {
        bool changed = true;
        while (changed) {
            changed = false;
            for (const auto& cl : clauses_) {
                bool sat = false;
                int unassigned = 0;
                int last = 0;
                for (int lit : cl) {
                    int v = lit_value(lit);
                    if (v == 1) {
                        sat = true;
                        break;
                    }
                    if (v == 0) {
                        ++unassigned;
                        last = lit;
                    }
                }
                if (sat) continue;
                if (unassigned == 0) return false;
                if (unassigned == 1) {
                    if (!set_lit(last)) return false;
                    changed = true;
                }
            }
        }
        return true;
    }

    int choose_lit() const {
        int best_lit = 0;
        int best_count = 1 << 30;
        for (int value = 1; value <= m_; ++value) {
            bool has_true = false;
            int count = 0;
            int first = 0;
            for (int cell = 0; cell < m_; ++cell) {
                int lit = var_of_oriented(cell, value, m_, transposed_);
                int v = lit_value(lit);
                if (v == 1) {
                    has_true = true;
                    break;
                }
                if (v == 0) {
                    ++count;
                    if (!first) first = lit;
                }
            }
            if (!has_true && count > 0 && count < best_count) {
                best_count = count;
                best_lit = first;
                if (count == 1) break;
            }
        }
        if (best_lit) return best_lit;
        for (int v = 1; v <= primary_vars_; ++v) {
            if (assign_[v] == 0) return v;
        }
        return 0;
    }

    void learn_current_assumption_clause() {
        Clause learned;
        learned.reserve(assumptions_.size());
        for (int lit : assumptions_) learned.push_back(-lit);
        clauses_.push_back(learned);
        proof_.add_rup(clause_to_pb(learned));
    }

    Result run_branch(int lit) {
        size_t mark = trail_.size();
        assumptions_.push_back(lit);
        Result result = Result::Unsat;
        if (!set_lit(lit)) {
            learn_current_assumption_clause();
        } else {
            result = dfs();
        }
        assumptions_.pop_back();
        undo(mark);
        return result;
    }

    Result dfs() {
        if (++nodes_ > node_limit_) return Result::Limit;
        if (!propagate()) {
            learn_current_assumption_clause();
            return Result::Unsat;
        }

        int lit = choose_lit();
        if (lit == 0) return Result::Sat;

        Result pos = run_branch(lit);
        if (pos != Result::Unsat) return pos;

        Result neg = run_branch(-lit);
        if (neg != Result::Unsat) return neg;

        learn_current_assumption_clause();
        return Result::Unsat;
    }
};

class GenericProofDPLL {
public:
    enum class Result { Unsat, Sat, Limit };

    GenericProofDPLL(int nvars, std::vector<Clause> clauses, ProofWriter& proof, long long node_limit)
        : nvars_(nvars),
          clauses_(std::move(clauses)),
          proof_(proof),
          node_limit_(node_limit),
          assign_(nvars_ + 1, 0) {}

    Result prove() { return dfs(); }

private:
    int nvars_;
    std::vector<Clause> clauses_;
    ProofWriter& proof_;
    long long node_limit_;
    long long nodes_ = 0;
    std::vector<int8_t> assign_;
    std::vector<int> trail_;
    std::vector<int> assumptions_;

    int lit_value(int lit) const {
        int8_t v = assign_[std::abs(lit)];
        if (v == 0) return 0;
        return ((lit > 0) == (v > 0)) ? 1 : -1;
    }

    bool set_lit(int lit) {
        int var = std::abs(lit);
        int8_t val = lit > 0 ? 1 : -1;
        if (assign_[var] == val) return true;
        if (assign_[var] == -val) return false;
        assign_[var] = val;
        trail_.push_back(var);
        return true;
    }

    void undo(size_t mark) {
        while (trail_.size() > mark) {
            assign_[trail_.back()] = 0;
            trail_.pop_back();
        }
    }

    bool propagate() {
        bool changed = true;
        while (changed) {
            changed = false;
            for (const auto& cl : clauses_) {
                bool sat = false;
                int unassigned = 0;
                int last = 0;
                for (int lit : cl) {
                    int v = lit_value(lit);
                    if (v == 1) {
                        sat = true;
                        break;
                    }
                    if (v == 0) {
                        ++unassigned;
                        last = lit;
                    }
                }
                if (sat) continue;
                if (unassigned == 0) return false;
                if (unassigned == 1) {
                    if (!set_lit(last)) return false;
                    changed = true;
                }
            }
        }
        return true;
    }

    int choose_lit() const {
        int best_lit = 0;
        int best_count = 1 << 30;
        for (const auto& cl : clauses_) {
            bool sat = false;
            int unassigned = 0;
            int first = 0;
            for (int lit : cl) {
                int v = lit_value(lit);
                if (v == 1) {
                    sat = true;
                    break;
                }
                if (v == 0) {
                    ++unassigned;
                    if (!first) first = lit;
                }
            }
            if (!sat && unassigned > 0 && unassigned < best_count) {
                best_count = unassigned;
                best_lit = first;
                if (best_count == 1) break;
            }
        }
        return best_lit;
    }

    void learn_current_assumption_clause() {
        Clause learned;
        learned.reserve(assumptions_.size());
        for (int lit : assumptions_) learned.push_back(-lit);
        clauses_.push_back(learned);
        proof_.add_rup(clause_to_pb(learned));
    }

    Result run_branch(int lit) {
        size_t mark = trail_.size();
        assumptions_.push_back(lit);
        Result result = Result::Unsat;
        if (!set_lit(lit)) {
            learn_current_assumption_clause();
        } else {
            result = dfs();
        }
        assumptions_.pop_back();
        undo(mark);
        return result;
    }

    Result dfs() {
        if (++nodes_ > node_limit_) return Result::Limit;
        if (!propagate()) {
            learn_current_assumption_clause();
            return Result::Unsat;
        }

        int lit = choose_lit();
        if (lit == 0) return Result::Sat;

        Result first = run_branch(lit);
        if (first != Result::Unsat) return first;

        Result second = run_branch(-lit);
        if (second != Result::Unsat) return second;

        learn_current_assumption_clause();
        return Result::Unsat;
    }
};

static bool prove_original_cnf_by_dpll(const CNF& cnf, ProofWriter& proof) {
    GenericProofDPLL dpll(cnf.nvars, cnf.clauses, proof, 1000000);
    return dpll.prove() == GenericProofDPLL::Result::Unsat;
}

static bool prove_direct_unsat_by_dpll(const CNF& cnf, const Shape& shape, ProofWriter& proof, bool transposed = false) {
    if (shape.m > 36) return false;
    emit_value_pairwise_amo(shape.m, proof, transposed);
    std::vector<Clause> clauses = build_primary_dpll_clauses(cnf, shape, transposed);
    PrimaryProofDPLL dpll(shape.m, transposed, std::move(clauses), proof, 500000);
    return dpll.prove() == PrimaryProofDPLL::Result::Unsat;
}

class AuxDPLL {
public:
    AuxDPLL(const CNF& cnf, std::vector<int8_t>& assign) : cnf_(cnf), assign_(assign) {}

    bool solve() { return dfs(); }

private:
    const CNF& cnf_;
    std::vector<int8_t>& assign_;
    std::vector<int> trail_;

    int lit_value(int lit) const {
        int8_t v = assign_[std::abs(lit)];
        if (v == 0) return 0;
        return ((lit > 0) == (v > 0)) ? 1 : -1;
    }

    bool set_lit(int lit) {
        int var = std::abs(lit);
        int8_t val = lit > 0 ? 1 : -1;
        if (assign_[var] == val) return true;
        if (assign_[var] == -val) return false;
        assign_[var] = val;
        trail_.push_back(var);
        return true;
    }

    void undo(size_t mark) {
        while (trail_.size() > mark) {
            assign_[trail_.back()] = 0;
            trail_.pop_back();
        }
    }

    bool propagate() {
        bool changed = true;
        while (changed) {
            changed = false;
            for (const auto& cl : cnf_.clauses) {
                bool sat = false;
                int unassigned = 0;
                int last = 0;
                for (int lit : cl) {
                    int v = lit_value(lit);
                    if (v == 1) {
                        sat = true;
                        break;
                    }
                    if (v == 0) {
                        ++unassigned;
                        last = lit;
                    }
                }
                if (sat) continue;
                if (unassigned == 0) return false;
                if (unassigned == 1) {
                    if (!set_lit(last)) return false;
                    changed = true;
                }
            }
        }
        return true;
    }

    int choose_lit() const {
        int best_lit = 0;
        int best_count = 1 << 30;
        for (const auto& cl : cnf_.clauses) {
            bool sat = false;
            int unassigned = 0;
            int first = 0;
            for (int lit : cl) {
                int v = lit_value(lit);
                if (v == 1) {
                    sat = true;
                    break;
                }
                if (v == 0) {
                    ++unassigned;
                    if (!first) first = lit;
                }
            }
            if (!sat && unassigned > 0 && unassigned < best_count) {
                best_count = unassigned;
                best_lit = first;
                if (best_count == 1) break;
            }
        }
        if (best_lit) return best_lit;
        return 0;
    }

    bool dfs() {
        if (!propagate()) return false;
        int lit = choose_lit();
        if (lit == 0) return true;

        size_t mark = trail_.size();
        if (set_lit(lit) && dfs()) return true;
        undo(mark);
        if (set_lit(-lit) && dfs()) return true;
        undo(mark);
        return false;
    }
};

static bool verify_assignment(const CNF& cnf, const std::vector<int8_t>& assign) {
    for (const auto& cl : cnf.clauses) {
        bool sat = false;
        for (int lit : cl) {
            int8_t v = assign[std::abs(lit)];
            if (v == 0) v = -1;
            if ((lit > 0 && v > 0) || (lit < 0 && v < 0)) {
                sat = true;
                break;
            }
        }
        if (!sat) return false;
    }
    return true;
}

static bool primary_clauses_satisfied_by_path(const CNF& cnf, int m, const std::vector<int>& path_cell, bool transposed) {
    int primary_limit = m * m;
    std::vector<char> primary_true(primary_limit + 1, 0);
    for (int value = 1; value <= m; ++value) {
        int cell = path_cell[value - 1];
        if (cell < 0 || cell >= m) return false;
        primary_true[var_of_oriented(cell, value, m, transposed)] = 1;
    }

    for (const auto& cl : cnf.clauses) {
        bool primary_only = true;
        for (int lit : cl) {
            if (std::abs(lit) > primary_limit) {
                primary_only = false;
                break;
            }
        }
        if (!primary_only) continue;

        bool sat = false;
        for (int lit : cl) {
            bool val = primary_true[std::abs(lit)];
            if ((lit > 0 && val) || (lit < 0 && !val)) {
                sat = true;
                break;
            }
        }
        if (!sat) return false;
    }
    return true;
}

static std::vector<int> serpentine_path(int n) {
    int m = n * n;
    std::vector<int> path_cell(m);
    for (int idx = 0; idx < m; ++idx) {
        int r = idx / n;
        int off = idx % n;
        int c = (r % 2 == 0) ? off : (n - 1 - off);
        path_cell[idx] = r * n + c;
    }
    return path_cell;
}

static int transform_cell(int cell, int n, int sym) {
    int r = cell / n, c = cell % n;
    int rr = r, cc = c;
    switch (sym) {
        case 0: rr = r; cc = c; break;
        case 1: rr = c; cc = n - 1 - r; break;
        case 2: rr = n - 1 - r; cc = n - 1 - c; break;
        case 3: rr = n - 1 - c; cc = r; break;
        case 4: rr = r; cc = n - 1 - c; break;
        case 5: rr = n - 1 - r; cc = c; break;
        case 6: rr = c; cc = r; break;
        case 7: rr = n - 1 - c; cc = n - 1 - r; break;
    }
    return rr * n + cc;
}

static int cheb_dist(int a, int b, int n) {
    int ar = a / n, ac = a % n;
    int br = b / n, bc = b % n;
    return std::max(std::abs(ar - br), std::abs(ac - bc));
}

static bool construct_path(const CNF& cnf, const Shape& shape, std::vector<int>& path_cell, bool transposed) {
    int n = shape.n, m = shape.m;
    std::vector<int> fixed_cell(m + 1, -1), fixed_value_for_cell(m, -1);
    std::vector<std::vector<char>> allowed(m + 1, std::vector<char>(m, 1));

    for (int lit : primary_units(cnf, m)) {
        if (lit > 0) continue;
        int var = -lit;
        int cell, value;
        decode_primary_var(var, m, transposed, cell, value);
        allowed[value][cell] = 0;
    }

    for (int lit : positive_primary_units(cnf, m)) {
        int cell, value;
        decode_primary_var(lit, m, transposed, cell, value);
        if (!allowed[value][cell]) return false;
        if ((fixed_cell[value] != -1 && fixed_cell[value] != cell) ||
            (fixed_value_for_cell[cell] != -1 && fixed_value_for_cell[cell] != value)) {
            return false;
        }
        fixed_cell[value] = cell;
        fixed_value_for_cell[cell] = value;
    }

    std::vector<int> base_serpent = serpentine_path(n);
    for (int sym = 0; sym < 8; ++sym) {
        for (int rev = 0; rev < 2; ++rev) {
            path_cell.assign(m, -1);
            for (int idx = 0; idx < m; ++idx) {
                int src = rev ? (m - 1 - idx) : idx;
                path_cell[idx] = transform_cell(base_serpent[src], n, sym);
            }
            bool serpent_ok = true;
            for (int value = 1; value <= m; ++value) {
                int cell = path_cell[value - 1];
                if (!allowed[value][cell] ||
                    (fixed_cell[value] != -1 && fixed_cell[value] != cell)) {
                    serpent_ok = false;
                    break;
                }
            }
            if (serpent_ok && primary_clauses_satisfied_by_path(cnf, m, path_cell, transposed)) return true;
        }
    }
    if (m > 64) return false;

    auto nb = king_neighbors(n);
    path_cell.assign(m, -1);
    uint64_t used = 0;
    std::vector<int> fixed_values;
    for (int value = 1; value <= m; ++value) {
        if (fixed_cell[value] != -1) fixed_values.push_back(value);
    }

    auto future_ok = [&](int value, int cell) {
        for (int fv : fixed_values) {
            if (fv > value && cheb_dist(cell, fixed_cell[fv], n) > fv - value) return false;
        }
        return true;
    };

    long long nodes = 0;
    const long long node_limit = (m <= 49) ? 2000000 : 75000000;

    std::function<bool(int, int, uint64_t)> dfs = [&](int value, int cell, uint64_t mask) -> bool {
        if (++nodes > node_limit) return false;
        if (!allowed[value][cell]) return false;
        path_cell[value - 1] = cell;
        if (value == m) return primary_clauses_satisfied_by_path(cnf, m, path_cell, transposed);

        int next_value = value + 1;
        std::vector<int> cand;
        if (fixed_cell[next_value] != -1) {
            int fc = fixed_cell[next_value];
            if (((mask >> fc) & 1ULL) || cheb_dist(cell, fc, n) > 1 || !allowed[next_value][fc]) return false;
            cand.push_back(fc);
        } else {
            for (int to : nb[cell]) {
                if ((mask >> to) & 1ULL) continue;
                if (!allowed[next_value][to]) continue;
                if (fixed_value_for_cell[to] != -1 && fixed_value_for_cell[to] != next_value) continue;
                cand.push_back(to);
            }
            std::sort(cand.begin(), cand.end(), [&](int a, int b) {
                auto onward = [&](int x) {
                    int deg = 0;
                    for (int y : nb[x]) {
                        if (!((mask >> y) & 1ULL) &&
                            allowed[std::min(next_value + 1, m)][y] &&
                            (fixed_value_for_cell[y] == -1 || fixed_value_for_cell[y] == next_value + 1)) {
                            ++deg;
                        }
                    }
                    return deg;
                };
                int da = onward(a), db = onward(b);
                if (da != db) return da < db;
                int next_fixed_a = 0, next_fixed_b = 0;
                for (int fv : fixed_values) {
                    if (fv > next_value) {
                        next_fixed_a = cheb_dist(a, fixed_cell[fv], n);
                        next_fixed_b = cheb_dist(b, fixed_cell[fv], n);
                        break;
                    }
                }
                return next_fixed_a > next_fixed_b;
            });
        }

        for (int to : cand) {
            if (!future_ok(next_value, to)) continue;
            if (dfs(next_value, to, mask | bit_for(to))) return true;
            for (int v = next_value; v <= m; ++v) path_cell[v - 1] = -1;
        }
        return false;
    };

    std::vector<int> starts;
    if (fixed_cell[1] != -1) {
        starts.push_back(fixed_cell[1]);
    } else {
        for (int c : base_serpent) {
            if (!allowed[1][c]) continue;
            if (fixed_value_for_cell[c] == -1 || fixed_value_for_cell[c] == 1) starts.push_back(c);
        }
    }
    for (int start : starts) {
        if (!future_ok(1, start)) continue;
        std::fill(path_cell.begin(), path_cell.end(), -1);
        if (dfs(1, start, bit_for(start))) return true;
    }
    return false;
}

static bool build_path_model(const CNF& cnf, const Shape& shape, std::vector<int8_t>& assign, bool transposed = false) {
    int m = shape.m;
    std::vector<int> path_cell;
    if (!construct_path(cnf, shape, path_cell, transposed)) return false;

    assign.assign(cnf.nvars + 1, 0);
    auto set_lit = [&](int lit) {
        int var = std::abs(lit);
        int8_t val = lit > 0 ? 1 : -1;
        if (assign[var] == -val) return false;
        assign[var] = val;
        return true;
    };

    std::vector<char> primary_true(m * m + 1, 0);
    for (int value = 1; value <= m; ++value) {
        int cell = path_cell[value - 1];
        if (cell < 0) return false;
        int lit = var_of_oriented(cell, value, m, transposed);
        primary_true[lit] = 1;
        if (!set_lit(lit)) return false;
    }
    for (int lit = 1; lit <= m * m; ++lit) {
        if (!primary_true[lit] && !set_lit(-lit)) return false;
    }

    for (const auto& cl : cnf.clauses) {
        if (cl.size() == 1) {
            int lit = cl[0];
            if (!set_lit(lit)) return false;
        }
    }

    AuxDPLL dpll(cnf, assign);
    if (!dpll.solve()) return false;
    for (int v = 1; v <= cnf.nvars; ++v) {
        if (assign[v] == 0) assign[v] = -1;
    }
    return verify_assignment(cnf, assign);
}

static void print_model(const std::vector<int8_t>& assign) {
    std::cout << "s SATISFIABLE\n";
    int col = 0;
    std::cout << "v";
    for (int v = 1; v < static_cast<int>(assign.size()); ++v) {
        int lit = assign[v] > 0 ? v : -v;
        std::cout << ' ' << lit;
        if (++col == 20) {
            std::cout << " 0\n";
            if (v + 1 < static_cast<int>(assign.size())) std::cout << "v";
            col = 0;
        }
    }
    if (col != 0) std::cout << " 0\n";
}

int main(int argc, char** argv) {
    if (argc != 3) {
        std::cerr << "usage: solver <formula.cnf> <out.pbp>\n";
        return 1;
    }

    CNF cnf;
    if (!parse_dimacs(argv[1], cnf)) {
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    Shape shape = detect_shape(cnf);
    if (!shape.cell_alo || shape.m <= 0) {
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    std::vector<int> units = primary_units(cnf, shape.m);
    bool has_direct = shape.direct_adjacency || shape.direct_adjacency_transposed;
    if (!units.empty() && has_direct) {
        for (bool transposed : {false, true}) {
            if (transposed ? !shape.direct_adjacency_transposed : !shape.direct_adjacency) continue;
            ACResult ac = run_arc_consistency(cnf, shape, nullptr, transposed);
            if (ac.unsat) {
                std::ofstream proof_file(argv[2]);
                if (!proof_file) {
                    std::cout << "s UNKNOWN\n";
                    return 0;
                }
                ProofWriter proof(proof_file, static_cast<int>(cnf.clauses.size()));
                derive_pb_scaffolding(shape.m, proof, transposed);
                ACResult checked = run_arc_consistency(cnf, shape, &proof, transposed);
                if (!checked.unsat) {
                    std::cout << "s UNKNOWN\n";
                    return 0;
                }
                proof.add_rup(">= 1");
                proof.finish_unsat();
                std::cout << "s UNSATISFIABLE\n";
                return 20;
            }
        }
        std::vector<int8_t> assign;
        if (build_path_model(cnf, shape, assign) || build_path_model(cnf, shape, assign, true)) {
            std::ofstream proof_file(argv[2]);
            print_model(assign);
            return 10;
        }
        {
            for (bool transposed : {false, true}) {
                if (transposed ? !shape.direct_adjacency_transposed : !shape.direct_adjacency) continue;
                std::ofstream proof_file(argv[2]);
                if (proof_file) {
                    ProofWriter proof(proof_file, static_cast<int>(cnf.clauses.size()));
                    derive_pb_scaffolding(shape.m, proof, transposed);
                    if (prove_direct_unsat_by_dpll(cnf, shape, proof, transposed)) {
                        proof.finish_unsat();
                        std::cout << "s UNSATISFIABLE\n";
                        return 20;
                    }
                }
            }
        }
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    std::vector<int8_t> assign;
    if (build_path_model(cnf, shape, assign) || build_path_model(cnf, shape, assign, true)) {
        std::ofstream proof_file(argv[2]);
        print_model(assign);
        return 10;
    }

    if (has_direct && shape.m <= 36) {
        for (bool transposed : {false, true}) {
            if (transposed ? !shape.direct_adjacency_transposed : !shape.direct_adjacency) continue;
            std::ofstream proof_file(argv[2]);
            if (proof_file) {
                ProofWriter proof(proof_file, static_cast<int>(cnf.clauses.size()));
                derive_pb_scaffolding(shape.m, proof, transposed);
                if (prove_direct_unsat_by_dpll(cnf, shape, proof, transposed)) {
                    proof.finish_unsat();
                    std::cout << "s UNSATISFIABLE\n";
                    return 20;
                }
            }
        }
    }

    std::cout << "s UNKNOWN\n";
    return 0;
}
