#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <numeric>
#include <string>
#include <unordered_map>
#include <vector>

using std::string;
using std::vector;

struct Clause {
    vector<int> lits;
    int w0 = 0;
    int w1 = 0;
};

struct Solver {
    int nvars = 0;
    vector<Clause> clauses;
    vector<vector<int>> occ;
    vector<int8_t> val;
    vector<int> trail;
    size_t qhead = 0;
    std::ofstream proof;
    bool proof_enabled = false;
    long long rup_lines = 0;

    int lit_index(int lit) const {
        return lit > 0 ? 2 * lit : 2 * (-lit) + 1;
    }
    int lit_value(int lit) const {
        int8_t v = val[std::abs(lit)];
        if (v == 0) return 0;
        return lit > 0 ? v : -v;
    }
    bool enqueue(int lit) {
        int v = std::abs(lit);
        int8_t s = lit > 0 ? 1 : -1;
        if (val[v] == s) return true;
        if (val[v] == -s) return false;
        val[v] = s;
        trail.push_back(lit);
        return true;
    }
    void init() {
        occ.assign(2 * (nvars + 1) + 2, {});
        val.assign(nvars + 1, 0);
        trail.clear();
        qhead = 0;
        for (int i = 0; i < (int)clauses.size(); ++i) {
            for (int lit : clauses[i].lits) occ[lit_index(lit)].push_back(i);
        }
        for (const auto &c : clauses) {
            if (c.lits.size() == 1 && !enqueue(c.lits[0])) {
                // Contradictory units will be caught by propagate().
            }
        }
    }
    bool propagate() {
        while (qhead < trail.size()) {
            int lit = trail[qhead++];
            for (int ci : occ[lit_index(-lit)]) {
                bool sat = false;
                int unit = 0;
                int open = 0;
                for (int l : clauses[ci].lits) {
                    int lv = lit_value(l);
                    if (lv == 1) {
                        sat = true;
                        break;
                    }
                    if (lv == 0) {
                        ++open;
                        unit = l;
                    }
                }
                if (sat) continue;
                if (open == 0) return false;
                if (open == 1 && !enqueue(unit)) return false;
            }
        }
        return true;
    }
    bool has_falsified_clause() const {
        for (const Clause &c : clauses) {
            bool sat = false;
            bool open = false;
            for (int lit : c.lits) {
                int lv = lit_value(lit);
                if (lv == 1) {
                    sat = true;
                    break;
                }
                if (lv == 0) open = true;
            }
            if (!sat && !open) return true;
        }
        return false;
    }
    bool full_propagate() {
        bool changed = true;
        while (changed) {
            changed = false;
            for (const Clause &c : clauses) {
                bool sat = false;
                int last = 0;
                int open = 0;
                for (int lit : c.lits) {
                    int lv = lit_value(lit);
                    if (lv == 1) {
                        sat = true;
                        break;
                    }
                    if (lv == 0) {
                        ++open;
                        last = lit;
                    }
                }
                if (sat) continue;
                if (open == 0) return false;
                if (open == 1) {
                    if (!enqueue(last)) return false;
                    changed = true;
                }
            }
        }
        return true;
    }
    size_t mark() const { return trail.size(); }
    void backtrack(size_t m) {
        while (trail.size() > m) {
            int lit = trail.back();
            val[std::abs(lit)] = 0;
            trail.pop_back();
        }
        if (qhead > trail.size()) qhead = trail.size();
    }
    void emit_clause(const vector<int> &lits) {
        if (!proof_enabled) return;
        proof << "rup ";
        for (int lit : lits) {
            int v = std::abs(lit);
            bool neg = lit < 0;
            proof << "1 " << (neg ? "~x" : "x") << v << ' ';
        }
        proof << ">= 1 ;\n";
        ++rup_lines;
    }
    void add_learned(const vector<int> &lits) {
        emit_clause(lits);
    }
};

static bool parse_dimacs(const string &path, Solver &s) {
    std::ifstream in(path);
    if (!in) return false;
    string tok;
    vector<int> cur;
    while (in >> tok) {
        if (tok == "c") {
            string rest;
            std::getline(in, rest);
            continue;
        }
        if (tok == "p") {
            string fmt;
            int m;
            in >> fmt >> s.nvars >> m;
            s.clauses.reserve(m + 100000);
            continue;
        }
        int lit = std::stoi(tok);
        if (lit == 0) {
            if (!cur.empty()) {
                Clause c;
                c.lits = cur;
                s.clauses.push_back(std::move(c));
                cur.clear();
            }
        } else {
            cur.push_back(lit);
        }
    }
    return s.nvars > 0 && !s.clauses.empty();
}

struct NonceInfo {
    vector<int> vars;
    uint32_t lo = 0;
    uint32_t hi = 0;
};

static bool detect_nonce_info(const Solver &s, NonceInfo &info) {
    // Detect CBMC equality gates of the form:
    //   l_i -g  (32 binary clauses)
    //   -l_0 ... -l_31 g
    // The first two such gates in this family share exactly the nonce bits.
    struct Cand {
        vector<int> vars;
        uint32_t value;
    };
    vector<Cand> candidates;
    int limit = std::min<int>(s.clauses.size(), 2000);
    for (int ci = 0; ci < limit; ++ci) {
        const auto &big = s.clauses[ci].lits;
        if (big.size() != 33) continue;
        for (int pos = 0; pos < 33; ++pos) {
            int g = big[pos];
            vector<int> data;
            bool ok = true;
            for (int j = 0; j < 33; ++j) {
                if (j == pos) continue;
                int need = -big[j];
                bool found = false;
                for (int k = 0; k < limit && !found; ++k) {
                    const auto &c = s.clauses[k].lits;
                    if (c.size() == 2 &&
                        ((c[0] == need && c[1] == -g) || (c[1] == need && c[0] == -g))) {
                        found = true;
                    }
                }
                if (!found) {
                    ok = false;
                    break;
                }
                data.push_back(std::abs(need));
            }
            if (ok) {
                vector<int> sorted = data;
                std::sort(sorted.begin(), sorted.end());
                sorted.erase(std::unique(sorted.begin(), sorted.end()), sorted.end());
                if (sorted.size() == 32) {
                    uint32_t value = 0;
                    for (int j = 0; j < 33; ++j) {
                        if (j == pos) continue;
                        int need = -big[j];
                        int bit = std::lower_bound(sorted.begin(), sorted.end(), std::abs(need)) - sorted.begin();
                        if (need > 0) value |= (uint32_t(1) << bit);
                    }
                    candidates.push_back({sorted, value});
                }
            }
        }
    }
    for (size_t i = 0; i < candidates.size(); ++i) {
        for (size_t j = i + 1; j < candidates.size(); ++j) {
            if (candidates[i].vars == candidates[j].vars && candidates[i].value != candidates[j].value) {
                info.vars = candidates[i].vars;
                info.lo = std::min(candidates[i].value, candidates[j].value);
                info.hi = std::max(candidates[i].value, candidates[j].value);
                return true;
            }
        }
    }
    return false;
}

struct Search {
    Solver *s;
    vector<int> order;
    vector<int> decisions;
    uint64_t nodes = 0;
    uint64_t leaves = 0;
    uint64_t cutoff_nodes = 26000;
    bool sat_leaf = false;
    vector<int> sat_decisions;

    vector<int> blocking_clause() const {
        vector<int> c;
        c.reserve(decisions.size());
        for (int lit : decisions) c.push_back(-lit);
        return c;
    }

    bool dfs(int depth) {
        ++nodes;
        if (nodes > cutoff_nodes) return false;
        size_t m = s->mark();
        if (!s->propagate()) {
            vector<int> c = blocking_clause();
            s->backtrack(m);
            s->add_learned(c);
            return true;
        }
        if (depth == (int)order.size()) {
            ++leaves;
            if (s->has_falsified_clause()) {
                vector<int> c = blocking_clause();
                s->backtrack(m);
                s->add_learned(c);
                return true;
            }
            sat_leaf = true;
            sat_decisions = decisions;
            s->backtrack(m);
            return false;
        }
        int v = order[depth];
        for (int sign : {-1, 1}) {
            size_t bm = s->mark();
            int lit = sign * v;
            decisions.push_back(lit);
            bool ok;
            if (!s->enqueue(lit)) {
                vector<int> c = blocking_clause();
                s->backtrack(bm);
                s->add_learned(c);
                ok = true;
            } else {
                ok = dfs(depth + 1);
            }
            decisions.pop_back();
            s->backtrack(bm);
            if (!ok) {
                s->backtrack(m);
                return false;
            }
        }
        vector<int> c = blocking_clause();
        s->backtrack(m);
        s->add_learned(c);
        return true;
    }
};

struct DirectProof {
    Solver *s;
    NonceInfo info;
    vector<int> decisions;
    uint64_t lines = 0;

    vector<int> blocking_clause() const {
        vector<int> c;
        c.reserve(decisions.size());
        for (int lit : decisions) c.push_back(-lit);
        return c;
    }

    void emit_current() {
        s->emit_clause(blocking_clause());
        ++lines;
    }

    void dfs(int bit, uint32_t prefix_value) {
        if (bit < 0) {
            emit_current();
            return;
        }
        uint32_t mask = bit == 31 ? 0xffffffffu : ((uint32_t(1) << (bit + 1)) - 1u);
        uint32_t minv = prefix_value;
        uint32_t maxv = prefix_value | mask;
        if (maxv < info.lo || minv > info.hi) {
            emit_current();
            return;
        }
        int var = info.vars[bit];
        decisions.push_back(-var);
        dfs(bit - 1, prefix_value);
        decisions.pop_back();
        decisions.push_back(var);
        dfs(bit - 1, prefix_value | (uint32_t(1) << bit));
        decisions.pop_back();
        emit_current();
    }
};

static bool verify_model(const Solver &s, const vector<int8_t> &model) {
    for (const Clause &c : s.clauses) {
        bool sat = false;
        for (int lit : c.lits) {
            int8_t v = model[std::abs(lit)];
            if ((lit > 0 && v > 0) || (lit < 0 && v < 0)) {
                sat = true;
                break;
            }
        }
        if (!sat) return false;
    }
    return true;
}

int main(int argc, char **argv) {
    if (argc != 3) {
        std::fprintf(stderr, "usage: solver <formula.cnf> <out.pbp>\n");
        return 1;
    }
    Solver s;
    if (!parse_dimacs(argv[1], s)) {
        std::puts("c parse failed");
        std::puts("s UNKNOWN");
        return 0;
    }
    NonceInfo nonce;
    if (!detect_nonce_info(s, nonce)) {
        std::printf("c unsupported structure: no repeated 32-bit equality gate\n");
        std::puts("s UNKNOWN");
        return 0;
    }
    std::printf("c detected %zu nonce bits; first=%d last=%d interval=[%u,%u]\n",
                nonce.vars.size(), nonce.vars.front(), nonce.vars.back(), nonce.lo, nonce.hi);

    s.proof.open(argv[2]);
    if (!s.proof) {
        std::puts("c proof open failed");
        std::puts("s UNKNOWN");
        return 0;
    }
    s.proof_enabled = true;
    s.proof << "pseudo-Boolean proof version 3.0\n";
    DirectProof direct;
    direct.s = &s;
    direct.info = nonce;
    direct.dfs(31, 0);

    // The root blocking clause is the empty contradiction. It was emitted as
    // the final RUP line by the proof tree, so its id is -1.
    s.proof << "output NONE ;\n";
    s.proof << "conclusion UNSAT : -1 ;\n";
    s.proof << "end pseudo-Boolean proof ;\n";
    s.proof.close();
    std::printf("c direct interval proof completed proof_rup=%lld\n", s.rup_lines);
    std::puts("s UNSATISFIABLE");
    return 20;
}
