#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <fstream>
#include <functional>
#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <vector>

using i64 = long long;

struct Params {
    int d = 0;
    int f = 0;
    int n = 0;
    i64 vars = 0;
    i64 clauses = 0;
    std::vector<i64> pow;
};

static std::vector<std::vector<int>> all_branches(int depth, int fanin) {
    std::vector<std::vector<int>> out;
    std::vector<int> cur(depth, 0);
    if (depth == 0) {
        out.push_back({});
        return out;
    }
    while (true) {
        out.push_back(cur);
        int p = depth - 1;
        while (p >= 0 && cur[p] == fanin - 1) {
            cur[p] = 0;
            --p;
        }
        if (p < 0) break;
        ++cur[p];
    }
    return out;
}

static i64 cantor_pair(i64 i, i64 j) {
    return (i + j) * (i + j + 1) / 2 + j;
}

static i64 unordered_pair_code(int a, int b) {
    i64 i = a, j = b;
    if (i < j) std::swap(i, j);
    return cantor_pair(i - j, j);
}

struct Encoder {
    Params p;
    i64 N = 0;
    i64 num_branches = 0;
    i64 swap_codes = 0;
    i64 input_var_count = 0;
    i64 update_start = 0;

    explicit Encoder(const Params& pp) : p(pp) {
        N = p.pow[p.d];
        num_branches = (p.pow[p.d] - 1) / (p.f - 1);
        swap_codes = p.f * (p.f + 1) / 2;
        input_var_count = N * (1LL + 1LL * p.d * (p.n - 1));
        i64 unit_clauses = 2 * N;
        i64 swap_choice_clauses = 1LL * (p.n - 1) * num_branches * p.f
                                * (1LL + 1LL * p.f * (p.f - 1) / 2);
        update_start = unit_clauses + swap_choice_clauses + 1;
    }

    i64 branch_index(const std::vector<int>& branch) const {
        i64 x = 0;
        for (int v : branch) x = x * p.f + v;
        return x;
    }

    i64 branch_num_partial(const std::vector<int>& branch) const {
        i64 ret = 0;
        i64 displacement = 0;
        for (int i = 0; i < (int)branch.size(); ++i) {
            ret = p.f * ret + branch[i];
            displacement += p.pow[i];
        }
        return ret + displacement;
    }

    i64 input_var(const std::vector<int>& branch, int formula, int level) const {
        i64 b = 0;
        for (int v : branch) b = b * p.f + v;
        return b + (1LL * p.d * formula + level) * N + 1;
    }

    i64 swap_var(int a, int b, const std::vector<int>& branch, int formula) const {
        return (num_branches * formula + branch_num_partial(branch)) * swap_codes
             + input_var_count + unordered_pair_code(a, b) + 1;
    }

    i64 leftmost_var(const std::vector<int>& branch, int input_num, int formula) const {
        std::vector<int> br = branch;
        br.push_back(input_num);
        while ((int)br.size() < p.d) br.push_back(0);
        return input_var(br, formula, p.d - 1 - (int)branch.size());
    }

    i64 update_clause_id(int formula, int depth, const std::vector<int>& branch,
                         int j, int k, int ell, int dir) const {
        const i64 per_depth = 2LL * p.pow[p.d + 1];
        const i64 L = p.pow[p.d - 1 - depth];
        const i64 per_branch = 2LL * p.f * p.f * L;
        i64 offset = 0;
        offset += 1LL * formula * p.d * per_depth;
        offset += 1LL * (p.d - 1 - depth) * per_depth;
        offset += branch_index(branch) * per_branch;
        offset += 1LL * j * (2LL * p.f * L);
        offset += 1LL * k * (2LL * L);
        offset += 2LL * ell + dir;
        return update_start + offset;
    }
};

static bool parse_dimacs_flat(const std::string& path, i64& vars, i64& clauses,
                              std::vector<int>& flat) {
    std::ifstream in(path);
    if (!in) return false;
    std::string tok;
    bool saw_header = false;
    while (in >> tok) {
        if (tok == "c") {
            std::string rest;
            std::getline(in, rest);
            continue;
        }
        if (tok == "p") {
            std::string fmt;
            in >> fmt >> vars >> clauses;
            if (fmt != "cnf" || vars <= 0 || clauses <= 0) return false;
            saw_header = true;
            flat.reserve((size_t)std::min<i64>(clauses * 4, 20000000));
            continue;
        }
        if (!saw_header) return false;
        char* end = nullptr;
        long v = std::strtol(tok.c_str(), &end, 10);
        if (end == tok.c_str() || *end != '\0') return false;
        if (v < std::numeric_limits<int>::min() || v > std::numeric_limits<int>::max()) return false;
        flat.push_back((int)v);
    }
    if (!saw_header) return false;
    i64 zeros = 0;
    for (int x : flat) zeros += (x == 0);
    return zeros == clauses;
}

static bool infer_equiv_params(i64 vars, i64 clauses, Params& out) {
    for (int d = 2; d <= 8; ++d) {
        for (int f = 2; f <= 16; ++f) {
            std::vector<i64> pw(d + 2, 1);
            bool ok = true;
            for (int i = 1; i <= d + 1; ++i) {
                if (pw[i - 1] > (std::numeric_limits<i64>::max() / f)) {
                    ok = false;
                    break;
                }
                pw[i] = pw[i - 1] * f;
            }
            if (!ok || pw[d] > 1000000) continue;
            i64 N = pw[d];
            i64 branches = (N - 1) / (f - 1);
            i64 codes = f * (f + 1) / 2;
            for (int n = 2; n <= 1000000; ++n) {
                i64 v = N * (1LL + 1LL * d * (n - 1)) + 1LL * (n - 1) * branches * codes;
                if (v > vars) break;
                if (v != vars) continue;
                i64 c1 = 2 * N; // equivalence-chain default: both don't-care sides are fixed
                i64 c2 = 1LL * (n - 1) * branches * f * (1LL + 1LL * f * (f - 1) / 2);
                i64 c3 = 2LL * d * N * f * (n - 1);
                if (c1 + c2 + c3 == clauses) {
                    out = Params{d, f, n, vars, clauses, pw};
                    return true;
                }
            }
        }
    }
    return false;
}

struct ClauseChecker {
    const std::vector<int>& flat;
    size_t pos = 0;
    i64 cid = 0;
    bool ok = true;

    explicit ClauseChecker(const std::vector<int>& ff) : flat(ff) {}

    void clause(const std::vector<i64>& lits) {
        if (!ok) return;
        ++cid;
        for (i64 lit : lits) {
            if (pos >= flat.size() || flat[pos] != lit) {
                ok = false;
                return;
            }
            ++pos;
        }
        if (pos >= flat.size() || flat[pos] != 0) {
            ok = false;
            return;
        }
        ++pos;
    }
};

struct ProofIds {
    std::vector<i64> init_neg_units;
    std::vector<i64> final_pos_units;
    i64 update_start = 0;
};

static bool verify_generated_formula(const Params& p, const std::vector<int>& flat, ProofIds& ids) {
    Encoder e(p);
    ClauseChecker cc(flat);
    const auto leaves = all_branches(p.d, p.f);

    for (const auto& br : leaves) {
        bool initial_true = true;
        for (int i = 0; i < p.d; i += 2) initial_true = initial_true && br[i] == 0;
        if (initial_true) cc.clause({e.input_var(br, 0, 0)});
    }
    for (const auto& br : leaves) {
        bool initial_true = true;
        for (int i = 0; i < p.d; i += 2) initial_true = initial_true && br[i] == 0;
        if (!initial_true) {
            ids.init_neg_units.push_back(cc.cid + 1);
            cc.clause({-e.input_var(br, 0, 0)});
        }
    }
    for (const auto& br : leaves) {
        bool terminal_false = true;
        for (int i = 1; i < p.d; i += 2) terminal_false = terminal_false && br[i] == 0;
        if (terminal_false) cc.clause({-e.input_var(br, p.n - 1, 0)});
    }
    for (const auto& br : leaves) {
        bool terminal_false = true;
        for (int i = 1; i < p.d; i += 2) terminal_false = terminal_false && br[i] == 0;
        if (!terminal_false) {
            ids.final_pos_units.push_back(cc.cid + 1);
            cc.clause({e.input_var(br, p.n - 1, 0)});
        }
    }

    for (int t = 0; t < p.n - 1; ++t) {
        for (int depth = 0; depth < p.d; ++depth) {
            for (const auto& br : all_branches(depth, p.f)) {
                for (int j = 0; j < p.f; ++j) {
                    std::vector<i64> atleast;
                    for (int k = 0; k < p.f; ++k) atleast.push_back(e.swap_var(j, k, br, t));
                    cc.clause(atleast);
                    for (int k = 0; k < p.f - 1; ++k) {
                        for (int kb = k + 1; kb < p.f; ++kb) {
                            cc.clause({-e.swap_var(j, k, br, t), -e.swap_var(j, kb, br, t)});
                        }
                    }
                }
            }
        }
    }

    ids.update_start = cc.cid + 1;

    for (int t = 0; t < p.n - 1; ++t) {
        for (int depth = p.d - 1; depth >= 0; --depth) {
            i64 L = p.pow[p.d - 1 - depth];
            for (const auto& br : all_branches(depth, p.f)) {
                for (int j = 0; j < p.f; ++j) {
                    for (int k = 0; k < p.f; ++k) {
                        i64 s = e.swap_var(j, k, br, t);
                        i64 v1 = e.leftmost_var(br, j, t);
                        i64 v2 = e.leftmost_var(br, k, t) + e.N;
                        for (int ell = 0; ell < L; ++ell) {
                            cc.clause({-s, -(v1 + ell), v2 + ell});
                            cc.clause({-s, -(v2 + ell), v1 + ell});
                        }
                    }
                }
            }
        }
    }

    return cc.ok && cc.pos == flat.size() && cc.cid == p.clauses && ids.update_start == e.update_start;
}

static void append_rpn_sum(std::ostream& out, const std::vector<std::string>& items) {
    out << items[0];
    for (size_t i = 1; i < items.size(); ++i) out << ' ' << items[i] << " +";
}

static std::vector<std::vector<std::pair<int, int>>> complete_matchings(int f) {
    std::vector<std::vector<std::pair<int, int>>> res;
    std::vector<std::pair<int, int>> cur;
    std::function<void(std::vector<int>)> dfs = [&](std::vector<int> rem) {
        if (rem.empty()) {
            res.push_back(cur);
            return;
        }
        int v = rem[0];
        std::vector<int> rest(rem.begin() + 1, rem.end());
        cur.push_back({v, v});
        dfs(rest);
        cur.pop_back();
        for (size_t idx = 0; idx < rest.size(); ++idx) {
            int u = rest[idx];
            std::vector<int> next;
            next.reserve(rest.size() - 1);
            for (size_t j = 0; j < rest.size(); ++j) if (j != idx) next.push_back(rest[j]);
            cur.push_back({v, u});
            dfs(next);
            cur.pop_back();
        }
    };
    std::vector<int> rem(f);
    for (int i = 0; i < f; ++i) rem[i] = i;
    dfs(rem);
    return res;
}

static std::vector<std::vector<std::pair<int, int>>> partial_matchings_postorder(int f) {
    std::vector<std::vector<std::pair<int, int>>> res;
    std::vector<std::pair<int, int>> cur;
    std::function<void(std::vector<int>)> dfs = [&](std::vector<int> rem) {
        if (rem.empty()) return;
        int v = rem[0];
        std::vector<int> rest(rem.begin() + 1, rem.end());
        cur.push_back({v, v});
        dfs(rest);
        cur.pop_back();
        for (size_t idx = 0; idx < rest.size(); ++idx) {
            int u = rest[idx];
            std::vector<int> next;
            next.reserve(rest.size() - 1);
            for (size_t j = 0; j < rest.size(); ++j) if (j != idx) next.push_back(rest[j]);
            cur.push_back({v, u});
            dfs(next);
            cur.pop_back();
        }
        res.push_back(cur);
    };
    std::vector<int> rem(f);
    for (int i = 0; i < f; ++i) rem[i] = i;
    dfs(rem);
    return res;
}

static std::vector<i64> leaves_under(const Encoder& e, const std::vector<int>& prefix,
                                    int formula, int level) {
    std::vector<i64> out;
    std::vector<int> cur = prefix;
    std::function<void()> dfs = [&]() {
        if ((int)cur.size() == e.p.d) {
            out.push_back(e.input_var(cur, formula, level));
            return;
        }
        for (int x = 0; x < e.p.f; ++x) {
            cur.push_back(x);
            dfs();
            cur.pop_back();
        }
    };
    dfs();
    return out;
}

static bool write_equiv_count_proof(const std::string& out_path, const Params& p, const ProofIds& ids) {
    Encoder e(p);
    std::ofstream out(out_path);
    if (!out) return false;

    const i64 K = p.pow[(p.d + 1) / 2]; // initial true leaves
    if ((e.N - K) <= K) return false;   // count argument would not contradict

    auto matchings = complete_matchings(p.f);
    auto partials = partial_matchings_postorder(p.f);
    i64 max_id = p.clauses;

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

    std::vector<std::string> init_terms;
    init_terms.reserve((size_t)e.N);
    for (i64 id : ids.init_neg_units) init_terms.push_back(std::to_string(id));
    for (const auto& br : all_branches(p.d, p.f)) {
        bool initial_true = true;
        for (int i = 0; i < p.d; i += 2) initial_true = initial_true && br[i] == 0;
        if (initial_true) init_terms.push_back("~x" + std::to_string(e.input_var(br, 0, 0)));
    }
    out << "pol ";
    append_rpn_sum(out, init_terms);
    out << " ;\n";
    i64 upper_id = ++max_id;

    for (int t = 0; t < p.n - 1; ++t) {
        for (int depth = p.d - 1; depth >= 0; --depth) {
            int level = p.d - 1 - depth;
            i64 L = p.pow[p.d - 1 - depth];
            i64 gate_size = p.pow[p.d - depth];
            for (const auto& br : all_branches(depth, p.f)) {
                auto src = leaves_under(e, br, t, level);
                auto dst = leaves_under(e, br, t, level + 1);
                out << "pbc ";
                for (i64 v : src) out << "1 x" << v << ' ';
                for (i64 v : dst) out << "1 ~x" << v << ' ';
                out << ">= " << gate_size << " : subproof\n";
                i64 neg_id = ++max_id;

                std::vector<std::vector<i64>> pair_id(p.f, std::vector<i64>(p.f, 0));
                for (int a = 0; a < p.f; ++a) {
                    for (int b = a; b < p.f; ++b) {
                        std::vector<std::string> refs;
                        refs.reserve((size_t)(a == b ? L : 2 * L));
                        if (a == b) {
                            for (int ell = 0; ell < L; ++ell) {
                                refs.push_back(std::to_string(e.update_clause_id(t, depth, br, a, b, ell, 1)));
                            }
                        } else {
                            for (int ell = 0; ell < L; ++ell) {
                                refs.push_back(std::to_string(e.update_clause_id(t, depth, br, a, b, ell, 1)));
                                refs.push_back(std::to_string(e.update_clause_id(t, depth, br, b, a, ell, 1)));
                            }
                        }
                        out << "pol ";
                        append_rpn_sum(out, refs);
                        out << " ;\n";
                        pair_id[a][b] = pair_id[b][a] = ++max_id;
                    }
                }

                for (const auto& m : matchings) {
                    std::vector<std::string> refs;
                    refs.reserve(m.size() + 1);
                    for (auto [a, b] : m) refs.push_back(std::to_string(pair_id[a][b]));
                    refs.push_back(std::to_string(neg_id));
                    out << "pol ";
                    append_rpn_sum(out, refs);
                    out << " s ;\n";
                    ++max_id;
                }

                for (const auto& part : partials) {
                    if (part.empty()) {
                        out << "rup >= 1 ;\n";
                    } else {
                        out << "rup ";
                        for (auto [a, b] : part) {
                            out << "1 ~x" << e.swap_var(a, b, br, t) << ' ';
                        }
                        out << ">= 1 ;\n";
                    }
                    ++max_id;
                }

                out << "qed pbc : -1 ;\n";
                i64 gate_count_id = ++max_id;

                out << "pol " << upper_id << ' ' << gate_count_id << " + ;\n";
                i64 new_upper_id = ++max_id;
                out << "del id " << upper_id << " ;\n";
                out << "del id " << gate_count_id << " ;\n";
                upper_id = new_upper_id;
            }
        }
    }

    std::vector<std::string> final_terms;
    final_terms.reserve(ids.final_pos_units.size() + 1);
    final_terms.push_back(std::to_string(upper_id));
    for (i64 id : ids.final_pos_units) final_terms.push_back(std::to_string(id));
    out << "pol ";
    append_rpn_sum(out, final_terms);
    out << " ;\n";
    i64 contradiction_id = ++max_id;

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

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

    i64 vars = 0, clauses = 0;
    std::vector<int> flat;
    if (!parse_dimacs_flat(argv[1], vars, clauses, flat)) {
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    Params p;
    if (!infer_equiv_params(vars, clauses, p)) {
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    ProofIds ids;
    if (!verify_generated_formula(p, flat, ids)) {
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    if (!write_equiv_count_proof(argv[2], p, ids)) {
        std::cout << "s UNKNOWN\n";
        return 0;
    }

    std::cout << "s UNSATISFIABLE\n";
    return 20;
}
