#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <numeric>
#include <random>
#include <string>
#include <unordered_set>
#include <vector>

using namespace std;

struct Formula {
    int vars = 0;
    vector<vector<int>> clauses;
};

static bool read_dimacs(const string& path, Formula& f) {
    ifstream in(path);
    if (!in) return false;
    string tok;
    int declared_clauses = 0;
    while (in >> tok) {
        if (tok == "c") {
            string rest;
            getline(in, rest);
        } else if (tok == "p") {
            string fmt;
            in >> fmt >> f.vars >> declared_clauses;
            if (fmt != "cnf" || f.vars <= 0 || declared_clauses < 0) return false;
            f.clauses.reserve(declared_clauses);
        } else {
            int lit = stoi(tok);
            vector<int> clause;
            while (lit != 0) {
                clause.push_back(lit);
                if (!(in >> lit)) return false;
            }
            f.clauses.push_back(std::move(clause));
        }
    }
    return f.vars > 0;
}

static uint64_t splitmix64(uint64_t x) {
    x += 0x9e3779b97f4a7c15ULL;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

static uint64_t line_key(const vector<int>& lits) {
    uint64_t h = 0x123456789abcdef0ULL;
    for (int v : lits) h ^= splitmix64((uint64_t)v + 0x100000001b3ULL + (h << 1));
    return h;
}

struct Detector {
    int n = 0;
    int m = 0;
    vector<pair<int, int>> missing_pairs;
};

static bool detect_battleship(const Formula& f, Detector& d) {
    int long_count = 0;
    int long_len = -1;
    unordered_set<uint64_t> actual;
    actual.reserve(f.clauses.size() * 2 + 1);
    for (const auto& c : f.clauses) {
        if ((int)c.size() > 2) {
            if (long_len < 0) long_len = (int)c.size();
            if ((int)c.size() != long_len) return false;
            vector<int> s = c;
            sort(s.begin(), s.end());
            for (int lit : s) {
                if (lit <= 0 || lit > f.vars) return false;
            }
            actual.insert(line_key(s));
            long_count++;
        } else if ((int)c.size() == 2) {
            if (c[0] >= 0 || c[1] >= 0) return false;
        } else {
            return false;
        }
    }
    int n = (int)llround(sqrt((double)long_count));
    if (n <= 1 || n * n != long_count) return false;
    if (f.vars % n != 0) return false;
    int m = f.vars / n;
    if (m != long_len) return false;

    unordered_set<uint64_t> expected;
    expected.reserve((size_t)n * n * 2 + 1);
    vector<int> line;
    line.reserve(m);
    for (int a = 0; a < n; ++a) {
        for (int b = 0; b < n; ++b) {
            line.clear();
            for (int x = 0; x < m; ++x) {
                int y = (int)(((long long)a * x + b) % n);
                line.push_back(x * n + y + 1);
            }
            sort(line.begin(), line.end());
            expected.insert(line_key(line));
        }
    }
    if (expected.size() != (size_t)n * n || actual.size() != expected.size()) return false;
    for (uint64_t h : expected) {
        if (!actual.count(h)) return false;
    }

    vector<vector<vector<unsigned char>>> seen(m, vector<vector<unsigned char>>(n, vector<unsigned char>(n, 0)));
    for (const auto& c : f.clauses) {
        if ((int)c.size() != 2) continue;
        int v1 = -c[0] - 1;
        int v2 = -c[1] - 1;
        if (v1 < 0 || v2 < 0 || v1 >= f.vars || v2 >= f.vars) return false;
        int x1 = v1 / n, y1 = v1 % n;
        int x2 = v2 / n, y2 = v2 % n;
        if (x1 != x2 || y1 == y2) return false;
        seen[x1][y1][y2] = seen[x1][y2][y1] = 1;
    }
    for (int x = 0; x < m; ++x) {
        for (int y1 = 0; y1 < n; ++y1) {
            for (int y2 = y1 + 1; y2 < n; ++y2) {
                if (!seen[x][y1][y2]) d.missing_pairs.push_back({x * n + y1, x * n + y2});
            }
        }
    }
    d.n = n;
    d.m = m;
    return true;
}

struct SearchResult {
    bool sat = false;
    vector<int> y_by_x;
    vector<pair<int, int>> fixed_cells;
};

static int64_t now_ms() {
    using namespace chrono;
    return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
}

static void add_cell_counts(int n, int x, int yy, vector<vector<int>>& cnt, vector<int>& miss) {
    for (int a = 0; a < n; ++a) {
        int b = (yy - (int)((long long)a * x % n) + n) % n;
        if (cnt[a][b] == 0) miss[a]--;
        cnt[a][b]++;
    }
}

static void build_greedy(int n, const vector<int>& xs, const vector<pair<int, int>>& fixed,
                         vector<int>& y, mt19937_64& rng) {
    int slots = (int)xs.size();
    y.assign(slots, -1);
    vector<vector<int>> cnt(n, vector<int>(n, 0));
    vector<int> miss(n, n);
    for (auto [x, yy] : fixed) add_cell_counts(n, x, yy, cnt, miss);

    for (int placed = 0; placed < slots; ++placed) {
        int best_i = -1, best_y = -1, best_gain = -1;
        for (int i = 0; i < slots; ++i) {
            if (y[i] >= 0) continue;
            int x = xs[i];
            for (int yy = 0; yy < n; ++yy) {
                int gain = 0;
                for (int a = 0; a < n; ++a) {
                    int b = (yy - (int)((long long)a * x % n) + n) % n;
                    if (cnt[a][b] == 0) gain++;
                }
                if (gain > best_gain || (gain == best_gain && (int)(rng() & 7) == 0)) {
                    best_gain = gain;
                    best_i = i;
                    best_y = yy;
                }
            }
        }
        if (best_i < 0) break;
        y[best_i] = best_y;
        add_cell_counts(n, xs[best_i], best_y, cnt, miss);
    }
    for (int i = 0; i < slots; ++i) {
        if (y[i] < 0) y[i] = (int)(rng() % n);
    }
}

static SearchResult solve_cover(int n, int m, const vector<pair<int, int>>& fixed, double seconds) {
    const int64_t deadline = now_ms() + (int64_t)(seconds * 1000.0);
    mt19937_64 rng(splitmix64((uint64_t)n * 1000003ULL + (uint64_t)m * 9176ULL));
    vector<unsigned char> fixed_col(m, 0);
    for (auto [x, yy] : fixed) {
        if (x < 0 || x >= m || yy < 0 || yy >= n) return {false, {}, {}};
        fixed_col[x] = 1;
    }
    vector<int> xs;
    for (int x = 0; x < m; ++x) {
        if (!fixed_col[x]) xs.push_back(x);
    }
    vector<int> y;

    for (int restart = 0; now_ms() < deadline; ++restart) {
        if (restart == 0) {
            build_greedy(n, xs, fixed, y, rng);
        } else {
            y.resize(xs.size());
            for (size_t i = 0; i < xs.size(); ++i) y[i] = (int)(rng() % n);
        }

        vector<vector<int>> cnt(n, vector<int>(n, 0));
        vector<int> miss(n, n);
        for (auto [x, yy] : fixed) add_cell_counts(n, x, yy, cnt, miss);
        for (size_t i = 0; i < xs.size(); ++i) {
            add_cell_counts(n, xs[i], y[i], cnt, miss);
        }
        int total = accumulate(miss.begin(), miss.end(), 0);
        vector<int> tabu(xs.size(), 0);
        int best_total = total;

        for (int step = 1; now_ms() < deadline && step <= 2000000; ++step) {
            if (total == 0) {
                vector<int> y_by_x(m, -1);
                for (size_t i = 0; i < xs.size(); ++i) y_by_x[xs[i]] = y[i];
                return {true, y_by_x, fixed};
            }

            vector<int> bad_a;
            for (int a = 0; a < n; ++a) {
                if (miss[a]) bad_a.push_back(a);
            }
            int a = bad_a[(size_t)(rng() % bad_a.size())];
            vector<int> bad_b;
            for (int b = 0; b < n; ++b) {
                if (cnt[a][b] == 0) bad_b.push_back(b);
            }
            int target_b = bad_b[(size_t)(rng() % bad_b.size())];

            int best_score = 1000000000;
            vector<pair<int, int>> candidates;
            for (size_t i = 0; i < xs.size(); ++i) {
                int x = xs[i];
                int new_y = (target_b + (int)((long long)a * x % n)) % n;
                int old_y = y[i];
                if (new_y == old_y) continue;
                int delta = 0;
                for (int aa = 0; aa < n; ++aa) {
                    int old_b = (old_y - (int)((long long)aa * x % n) + n) % n;
                    int new_b = (new_y - (int)((long long)aa * x % n) + n) % n;
                    if (old_b == new_b) continue;
                    if (cnt[aa][old_b] == 1) delta++;
                    if (cnt[aa][new_b] == 0) delta--;
                }
                int score = total + delta;
                if (tabu[i] > step && score >= best_total) continue;
                if (score < best_score) {
                    best_score = score;
                    candidates.clear();
                    candidates.push_back({(int)i, new_y});
                } else if (score == best_score) {
                    candidates.push_back({(int)i, new_y});
                }
            }
            if (candidates.empty()) break;
            auto [idx, new_y] = candidates[(size_t)(rng() % candidates.size())];
            int x = xs[idx];
            int old_y = y[idx];
            int delta = 0;
            for (int aa = 0; aa < n; ++aa) {
                int old_b = (old_y - (int)((long long)aa * x % n) + n) % n;
                int new_b = (new_y - (int)((long long)aa * x % n) + n) % n;
                if (old_b == new_b) continue;
                if (cnt[aa][old_b] == 1) {
                    miss[aa]++;
                    delta++;
                }
                cnt[aa][old_b]--;
                if (cnt[aa][new_b] == 0) {
                    miss[aa]--;
                    delta--;
                }
                cnt[aa][new_b]++;
            }
            y[idx] = new_y;
            total += delta;
            best_total = min(best_total, total);
            tabu[idx] = step + 5 + (int)(rng() % 31);
        }
    }
    return {false, {}, {}};
}

static bool verify_model(const Formula& f, const vector<int>& assign) {
    for (const auto& c : f.clauses) {
        bool ok = false;
        for (int lit : c) {
            int v = abs(lit);
            if (v <= 0 || v > f.vars) return false;
            bool val = assign[v] > 0;
            if ((lit > 0 && val) || (lit < 0 && !val)) {
                ok = true;
                break;
            }
        }
        if (!ok) return false;
    }
    return true;
}

static void print_model(const vector<int>& assign) {
    cout << "s SATISFIABLE\n";
    const int nvars = (int)assign.size() - 1;
    for (int i = 1; i <= nvars; ++i) {
        if (i == 1 || (i - 1) % 20 == 0) cout << "v ";
        cout << (assign[i] ? i : -i) << ' ';
        if (i % 20 == 0 || i == nvars) cout << "0\n";
    }
}

struct SmallProofState {
    int n = 0;
    int m = 0;
    int vars = 0;
    vector<vector<int>> lines;
    vector<vector<int>> var_lines;
    uint64_t all_covered = 0;
    int64_t deadline = 0;
    uint64_t nodes = 0;
    ostringstream proof;
};

static void proof_clause(ostringstream& out, const vector<int>& selected) {
    if (selected.empty()) {
        out << "rup >= 1 ;\n";
        return;
    }
    out << "rup ";
    for (int v : selected) out << "1 ~x" << (v + 1) << ' ';
    out << ">= 1 ;\n";
}

static bool small_dfs(SmallProofState& st, vector<int>& selected, uint64_t used_cols, uint64_t covered) {
    st.nodes++;
    if ((st.nodes & 0x3fffU) == 0 && now_ms() > st.deadline) throw runtime_error("proof timeout");
    if (covered == st.all_covered) return true;

    int best_line = -1;
    vector<int> best_avail;
    for (int li = 0; li < (int)st.lines.size(); ++li) {
        if ((covered >> li) & 1ULL) continue;
        vector<int> avail;
        for (int v : st.lines[li]) {
            int x = v / st.n;
            if (((used_cols >> x) & 1ULL) == 0) avail.push_back(v);
        }
        if (avail.empty()) {
            proof_clause(st.proof, selected);
            return false;
        }
        if (best_line < 0 || avail.size() < best_avail.size()) {
            best_line = li;
            best_avail.swap(avail);
            if (best_avail.size() == 1) break;
        }
    }

    sort(best_avail.begin(), best_avail.end(), [&](int lhs, int rhs) {
        int lc = 0, rc = 0;
        for (int li : st.var_lines[lhs]) lc += (((covered >> li) & 1ULL) == 0);
        for (int li : st.var_lines[rhs]) rc += (((covered >> li) & 1ULL) == 0);
        return lc > rc;
    });

    for (int v : best_avail) {
        int x = v / st.n;
        if ((used_cols >> x) & 1ULL) continue;
        uint64_t next_covered = covered;
        for (int li : st.var_lines[v]) next_covered |= (1ULL << li);
        selected.push_back(v);
        bool sat = small_dfs(st, selected, used_cols | (1ULL << x), next_covered);
        selected.pop_back();
        if (sat) return true;
    }
    proof_clause(st.proof, selected);
    return false;
}

static bool try_small_unsat_proof(const Formula& f, const Detector& d, const string& proof_path) {
    if (d.n > 5 || d.n * d.n >= 63 || d.m >= 63 || !d.missing_pairs.empty()) return false;
    if (d.m >= 2 * d.n - 1) return false;

    SmallProofState st;
    st.n = d.n;
    st.m = d.m;
    st.vars = f.vars;
    st.deadline = now_ms() + 25000;
    st.all_covered = (1ULL << (d.n * d.n)) - 1ULL;
    st.lines.reserve(d.n * d.n);
    st.var_lines.assign(f.vars, {});

    for (int a = 0; a < d.n; ++a) {
        for (int b = 0; b < d.n; ++b) {
            vector<int> line;
            for (int x = 0; x < d.m; ++x) {
                int y = (int)(((long long)a * x + b) % d.n);
                int v = x * d.n + y;
                st.var_lines[v].push_back((int)st.lines.size());
                line.push_back(v);
            }
            st.lines.push_back(std::move(line));
        }
    }
    st.proof << "pseudo-Boolean proof version 3.0\n";
    vector<int> selected;
    bool sat = true;
    try {
        sat = small_dfs(st, selected, 0, 0);
    } catch (const exception&) {
        return false;
    }
    if (sat) return false;
    st.proof << "output NONE ;\n";
    st.proof << "conclusion UNSAT : -1 ;\n";
    st.proof << "end pseudo-Boolean proof ;\n";

    ofstream out(proof_path);
    if (!out) return false;
    out << st.proof.str();
    return true;
}

struct SquareProofMaps {
    int n = 0;
    vector<vector<int>> line_id;
    vector<vector<vector<int>>> bin_id;
};

static bool build_square_proof_maps(const Formula& f, const Detector& d, SquareProofMaps& mp) {
    if (d.n <= 1 || d.m != d.n || !d.missing_pairs.empty()) return false;
    int n = d.n;
    mp.n = n;
    mp.line_id.assign(n, vector<int>(n, 0));
    mp.bin_id.assign(n, vector<vector<int>>(n, vector<int>(n, 0)));

    for (int cid = 1; cid <= (int)f.clauses.size(); ++cid) {
        const auto& c = f.clauses[cid - 1];
        if ((int)c.size() > 2) {
            if ((int)c.size() != n) return false;
            vector<int> ys(n, -1);
            for (int lit : c) {
                if (lit <= 0 || lit > f.vars) return false;
                int v = lit - 1;
                int x = v / n;
                int y = v % n;
                if (x < 0 || x >= n || ys[x] != -1) return false;
                ys[x] = y;
            }
            for (int x = 0; x < n; ++x) {
                if (ys[x] < 0) return false;
            }
            int b = ys[0];
            int a = (ys[1] - ys[0] + n) % n;
            for (int x = 0; x < n; ++x) {
                if (ys[x] != (int)(((long long)a * x + b) % n)) return false;
            }
            if (mp.line_id[a][b] != 0) return false;
            mp.line_id[a][b] = cid;
        } else if ((int)c.size() == 2) {
            if (c[0] >= 0 || c[1] >= 0) return false;
            int v1 = -c[0] - 1;
            int v2 = -c[1] - 1;
            if (v1 < 0 || v2 < 0 || v1 >= f.vars || v2 >= f.vars) return false;
            int x1 = v1 / n, y1 = v1 % n;
            int x2 = v2 / n, y2 = v2 % n;
            if (x1 != x2 || x1 < 0 || x1 >= n || y1 == y2) return false;
            if (y1 > y2) swap(y1, y2);
            if (mp.bin_id[x1][y1][y2] != 0) return false;
            mp.bin_id[x1][y1][y2] = cid;
        } else {
            return false;
        }
    }

    for (int a = 0; a < n; ++a) {
        for (int b = 0; b < n; ++b) {
            if (mp.line_id[a][b] == 0) return false;
        }
    }
    for (int x = 0; x < n; ++x) {
        for (int y1 = 0; y1 < n; ++y1) {
            for (int y2 = y1 + 1; y2 < n; ++y2) {
                if (mp.bin_id[x][y1][y2] == 0) return false;
            }
        }
    }
    return true;
}

static void emit_id_sum(ofstream& out, const vector<int>& ids) {
    out << ids[0];
    for (size_t i = 1; i < ids.size(); ++i) out << ' ' << ids[i] << " +";
}

static int emit_square_pol_sum(ofstream& out, const vector<int>& ids, int& next_id) {
    out << "pol ";
    emit_id_sum(out, ids);
    out << " ;\n";
    return next_id++;
}

static bool try_square_unsat_proof(const Formula& f, const Detector& d, const string& proof_path) {
    SquareProofMaps mp;
    if (!build_square_proof_maps(f, d, mp)) return false;
    int n = mp.n;
    ofstream out(proof_path);
    if (!out) return false;
    int next_id = (int)f.clauses.size() + 1;

    out << "pseudo-Boolean proof version 3.0\n";

    // For each physical column, derive the cardinality AMO
    //   sum_y ~x_{column,y} >= n-1
    // from the pairwise binary exclusions using a short cutting-planes chain.
    vector<int> col_amo(n, 0);
    for (int x = 0; x < n; ++x) {
        int prev = mp.bin_id[x][0][1];
        for (int k = 3; k <= n; ++k) {
            vector<int> pair_ids;
            pair_ids.reserve(k - 1);
            for (int y = 0; y < k - 1; ++y) pair_ids.push_back(mp.bin_id[x][y][k - 1]);
            out << "pol ";
            emit_id_sum(out, pair_ids);
            out << ' ' << prev << ' ' << (k - 2) << " * + " << (k - 1) << " d ;\n";
            prev = next_id++;
        }
        col_amo[x] = prev;
    }

    vector<int> all_lines;
    all_lines.reserve((size_t)n * n);
    for (int a = 0; a < n; ++a) {
        for (int b = 0; b < n; ++b) all_lines.push_back(mp.line_id[a][b]);
    }

    out << "pol ";
    emit_id_sum(out, all_lines);
    out << ' ' << n << " d ;\n";
    int total_lower = next_id++;

    int total_upper = emit_square_pol_sum(out, col_amo, next_id);

    vector<int> col_alo(2, 0);
    for (int c = 0; c < 2; ++c) {
        vector<int> ids;
        ids.reserve(n);
        ids.push_back(total_lower);
        for (int x = 0; x < n; ++x) {
            if (x != c) ids.push_back(col_amo[x]);
        }
        col_alo[c] = emit_square_pol_sum(out, ids, next_id);
    }

    // With exactly n occupied cells forced globally, every modular line can
    // contain at most one occupied cell.  Derive the AMO for each line.
    for (int a = 0; a < n; ++a) {
        for (int b = 0; b < n; ++b) {
            out << "pol " << total_upper << ' ' << n << " *";
            for (int aa = 0; aa < n; ++aa) {
                for (int bb = 0; bb < n; ++bb) {
                    if (aa == a && bb == b) continue;
                    out << ' ' << mp.line_id[aa][bb] << " +";
                }
            }
            out << " ;\n";
            next_id++;
        }
    }

    // Any selected cell in column 0 and any selected cell in adjacent column 1
    // share exactly one modular line, so the line AMOs RUP-imply all complete
    // bipartite cross exclusions between these two columns.
    vector<vector<int>> cross(n, vector<int>(n, 0));
    for (int y0 = 0; y0 < n; ++y0) {
        for (int y1 = 0; y1 < n; ++y1) {
            int v0 = y0 + 1;
            int v1 = n + y1 + 1;
            out << "rup 1 ~x" << v0 << " 1 ~x" << v1 << " >= 1 ;\n";
            cross[y0][y1] = next_id++;
        }
    }

    // Column 1 must contain a selected cell.  Combining that ALO with every
    // cross exclusion for a fixed cell in column 0 forces that cell false.
    vector<int> col0_units;
    col0_units.reserve(n);
    for (int y0 = 0; y0 < n; ++y0) {
        vector<int> ids;
        ids.reserve(n + 1);
        ids.push_back(col_alo[1]);
        for (int y1 = 0; y1 < n; ++y1) ids.push_back(cross[y0][y1]);
        out << "pol ";
        emit_id_sum(out, ids);
        out << ' ' << n << " d ;\n";
        col0_units.push_back(next_id++);
    }

    vector<int> final_ids;
    final_ids.reserve(n + 1);
    final_ids.push_back(col_alo[0]);
    final_ids.insert(final_ids.end(), col0_units.begin(), col0_units.end());
    int contradiction = emit_square_pol_sum(out, final_ids, next_id);

    out << "output NONE ;\n";
    out << "conclusion UNSAT : " << contradiction << " ;\n";
    out << "end pseudo-Boolean proof ;\n";
    return (bool)out;
}

struct Cdcl {
    vector<vector<int>> clauses;
    vector<int> val, level, reason, trail;
    vector<double> activity;
    int qhead = 0;
    int decision_level = 0;
    int64_t deadline = 0;
    ostringstream proof;

    explicit Cdcl(const Formula& f) {
        clauses = f.clauses;
        int n = f.vars;
        val.assign(n + 1, 0);
        level.assign(n + 1, 0);
        reason.assign(n + 1, -1);
        activity.assign(n + 1, 0.0);
        for (const auto& c : clauses) {
            for (int lit : c) activity[abs(lit)] += 1.0;
        }
    }

    int lit_value(int lit) const {
        int v = val[abs(lit)];
        if (v == 0) return 0;
        bool t = (v > 0);
        return (lit > 0) == t ? 1 : -1;
    }

    bool enqueue(int lit, int why) {
        int v = abs(lit);
        int want = lit > 0 ? 1 : -1;
        if (val[v] != 0) return val[v] == want;
        val[v] = want;
        level[v] = decision_level;
        reason[v] = why;
        trail.push_back(lit);
        return true;
    }

    int propagate() {
        bool changed = true;
        while (changed) {
            if (now_ms() > deadline) throw runtime_error("cdcl timeout");
            changed = false;
            for (int ci = 0; ci < (int)clauses.size(); ++ci) {
                const auto& c = clauses[ci];
                bool sat = false;
                int unassigned = 0;
                int unit_lit = 0;
                for (int lit : c) {
                    int lv = lit_value(lit);
                    if (lv > 0) {
                        sat = true;
                        break;
                    }
                    if (lv == 0) {
                        unassigned++;
                        unit_lit = lit;
                        if (unassigned > 1) {
                            // Still need to scan for a satisfied literal.
                        }
                    }
                }
                if (sat) continue;
                if (unassigned == 0) return ci;
                if (unassigned == 1) {
                    if (!enqueue(unit_lit, ci)) return ci;
                    changed = true;
                }
            }
        }
        return -1;
    }

    void backtrack(int lvl) {
        while (!trail.empty() && level[abs(trail.back())] > lvl) {
            int v = abs(trail.back());
            val[v] = 0;
            level[v] = 0;
            reason[v] = -1;
            trail.pop_back();
        }
        decision_level = lvl;
    }

    static void emit_clause(ostringstream& out, const vector<int>& clause) {
        if (clause.empty()) {
            out << "rup >= 1 ;\n";
            return;
        }
        out << "rup ";
        for (int lit : clause) {
            if (lit > 0) out << "1 x" << lit << ' ';
            else out << "1 ~x" << -lit << ' ';
        }
        out << ">= 1 ;\n";
    }

    vector<int> analyze(int confl, int& backtrack_level) {
        vector<unsigned char> seen(val.size(), 0);
        vector<int> learned;
        int path_c = 0;
        int p = 0;
        int idx = (int)trail.size() - 1;
        const vector<int>* c = &clauses[confl];

        while (true) {
            for (int lit : *c) {
                int v = abs(lit);
                if (seen[v] || level[v] == 0) continue;
                seen[v] = 1;
                activity[v] += 1.0;
                if (level[v] == decision_level) path_c++;
                else learned.push_back(lit);
            }
            while (idx >= 0 && !seen[abs(trail[idx])]) idx--;
            if (idx < 0) break;
            p = trail[idx];
            int pv = abs(p);
            seen[pv] = 0;
            path_c--;
            idx--;
            if (path_c <= 0) break;
            int r = reason[pv];
            if (r < 0) break;
            c = &clauses[r];
        }

        learned.insert(learned.begin(), -p);
        sort(learned.begin() + 1, learned.end(), [](int a, int b) {
            return abs(a) == abs(b) ? a < b : abs(a) < abs(b);
        });
        learned.erase(unique(learned.begin() + 1, learned.end()), learned.end());

        backtrack_level = 0;
        for (size_t i = 1; i < learned.size(); ++i) {
            backtrack_level = max(backtrack_level, level[abs(learned[i])]);
        }
        return learned;
    }

    int pick_branch_var() const {
        int best = 0;
        double ba = -1.0;
        for (int v = 1; v < (int)val.size(); ++v) {
            if (val[v] == 0 && activity[v] > ba) {
                ba = activity[v];
                best = v;
            }
        }
        return best;
    }

    int solve(const string& proof_path, double seconds) {
        deadline = now_ms() + (int64_t)(seconds * 1000.0);
        proof << "pseudo-Boolean proof version 3.0\n";
        try {
            while (true) {
                int confl = propagate();
                if (confl >= 0) {
                    if (decision_level == 0) {
                        emit_clause(proof, {});
                        proof << "output NONE ;\nconclusion UNSAT : -1 ;\nend pseudo-Boolean proof ;\n";
                        ofstream out(proof_path);
                        if (!out) return 0;
                        out << proof.str();
                        return 20;
                    }
                    int bt = 0;
                    vector<int> learned = analyze(confl, bt);
                    emit_clause(proof, learned);
                    clauses.push_back(learned);
                    int cid = (int)clauses.size() - 1;
                    backtrack(bt);
                    if (learned.empty()) {
                        emit_clause(proof, {});
                        proof << "output NONE ;\nconclusion UNSAT : -1 ;\nend pseudo-Boolean proof ;\n";
                        ofstream out(proof_path);
                        if (!out) return 0;
                        out << proof.str();
                        return 20;
                    }
                    enqueue(learned[0], cid);
                } else {
                    int v = pick_branch_var();
                    if (v == 0) return 10;
                    decision_level++;
                    enqueue(v, -1);
                }
            }
        } catch (const exception&) {
            return 0;
        }
    }
};

static int try_cdcl_unsat_proof(const Formula& f, const Detector& d, const string& proof_path) {
    (void)f;
    (void)d;
    (void)proof_path;
    return 0;
#if 0
    if (!d.missing_pairs.empty()) return 0;
    if (d.n > 14) return 0;
    Cdcl cdcl(f);
    return cdcl.solve(proof_path, 120.0);
#endif
}

int main(int argc, char** argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: solver <formula.cnf> <out.pbp>\n");
        return 1;
    }
    Formula f;
    if (!read_dimacs(argv[1], f)) {
        cout << "c parse failed\ns UNKNOWN\n";
        return 0;
    }
    Detector d;
    if (!detect_battleship(f, d)) {
        cout << "c unsupported CNF structure\ns UNKNOWN\n";
        return 0;
    }
    cout << "c detected modular battleship cover n=" << d.n << " m=" << d.m << "\n";

    if (d.m == d.n) {
        if (d.missing_pairs.empty() && try_square_unsat_proof(f, d, argv[2])) {
            cout << "c emitted cutting-planes proof for square full-binary instance\n";
            cout << "s UNSATISFIABLE\n";
            return 20;
        }
        cout << "c square instance is outside the supported proof regime\n";
        cout << "s UNKNOWN\n";
        return 0;
    }
    if (try_small_unsat_proof(f, d, argv[2])) {
        cout << "c emitted specialized tree RUP proof for small low-column instance\n";
        cout << "s UNSATISFIABLE\n";
        return 20;
    }

    double budget = 30.0;
    if (d.n <= 12) budget = 5.0;
    if (d.n >= 27) budget = 60.0;
    SearchResult r;
    if (!d.missing_pairs.empty()) {
        cout << "c trying " << d.missing_pairs.size() << " missing binary-pair relaxations\n";
        for (auto [p, q] : d.missing_pairs) {
            int x1 = p / d.n, y1 = p % d.n;
            int x2 = q / d.n, y2 = q % d.n;
            if (x1 != x2) continue;
            vector<pair<int, int>> fixed = {{x1, y1}, {x2, y2}};
            r = solve_cover(d.n, d.m, fixed, budget);
            if (r.sat) break;
        }
    }
    if (!r.sat) r = solve_cover(d.n, d.m, {}, budget);
    if (!r.sat) {
        int cdcl = try_cdcl_unsat_proof(f, d, argv[2]);
        if (cdcl == 20) {
            cout << "c emitted CDCL RUP proof for residual UNSAT instance\n";
            cout << "s UNSATISFIABLE\n";
            return 20;
        }
        cout << "c no cover found within high-level search budget; no UNSAT proof emitted\n";
        cout << "s UNKNOWN\n";
        return 0;
    }

    vector<int> assign(f.vars + 1, 0);
    for (int x = 0; x < d.m; ++x) {
        int yy = r.y_by_x[x];
        if (yy < 0) continue;
        if (yy >= d.n) {
            cout << "c internal bad y value\ns UNKNOWN\n";
            return 0;
        }
        assign[x * d.n + yy + 1] = 1;
    }
    for (auto [x, yy] : r.fixed_cells) {
        assign[x * d.n + yy + 1] = 1;
    }
    if (!verify_model(f, assign)) {
        cout << "c reconstructed assignment failed original clause verification\ns UNKNOWN\n";
        return 0;
    }
    print_model(assign);
    return 10;
}
