#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

using Clause = std::vector<int>;

struct Formula {
    int nvars = 0;
    std::vector<Clause> clauses; // 0-based; VeriPB IDs are index+1.
};

struct PairStructure {
    std::vector<int> arcs;
    std::unordered_set<int> arc_set;
    std::unordered_map<int, int> comp_of;
    std::unordered_map<int, std::vector<int>> support_of;
};

struct RowSystem {
    std::vector<std::vector<int>> rows;
    std::vector<int> row_clause_ids; // 0 means derive row by RUP.
    std::vector<std::vector<int>> cols;
    std::vector<std::vector<int>> edges;
};

static std::string lit_name(int lit) {
    if (lit > 0) return "x" + std::to_string(lit);
    return "~x" + std::to_string(-lit);
}

static bool read_dimacs(const std::string& path, Formula& f) {
    std::ifstream in(path);
    if (!in) return false;
    std::string tok;
    Clause current;
    while (in >> tok) {
        if (tok.empty()) continue;
        if (tok[0] == 'c') {
            std::string rest;
            std::getline(in, rest);
            continue;
        }
        if (tok == "p") {
            std::string cnf;
            int m = 0;
            in >> cnf >> f.nvars >> m;
            f.clauses.reserve(m);
            continue;
        }
        int lit = std::stoi(tok);
        if (lit == 0) {
            if (!current.empty()) {
                f.clauses.push_back(current);
                current.clear();
            }
        } else {
            current.push_back(lit);
        }
    }
    if (!current.empty()) f.clauses.push_back(current);
    return f.nvars > 0 && !f.clauses.empty();
}

static std::string key_of(const std::vector<int>& xs) {
    std::string s;
    for (int x : xs) {
        s += std::to_string(x);
        s.push_back(',');
    }
    return s;
}

static std::vector<int> sorted_intersection(const std::vector<int>& a, const std::unordered_set<int>& b) {
    std::vector<int> out;
    out.reserve(a.size());
    for (int x : a) {
        if (b.count(x)) out.push_back(x);
    }
    std::sort(out.begin(), out.end());
    out.erase(std::unique(out.begin(), out.end()), out.end());
    return out;
}

static PairStructure parse_pairs(const Formula& f) {
    std::set<std::pair<int, int>> pos_pairs;
    std::set<std::pair<int, int>> neg_pairs;
    std::unordered_map<int, std::vector<int>> long_support;

    for (const Clause& c : f.clauses) {
        if (c.size() == 2) {
            if (c[0] > 0 && c[1] > 0) {
                int a = std::min(c[0], c[1]);
                int b = std::max(c[0], c[1]);
                pos_pairs.insert({a, b});
            } else if (c[0] < 0 && c[1] < 0) {
                int a = std::min(-c[0], -c[1]);
                int b = std::max(-c[0], -c[1]);
                neg_pairs.insert({a, b});
            }
        }
        if (c.size() >= 3) {
            int negs = 0;
            int neg_var = 0;
            std::vector<int> pos;
            bool all_vars = true;
            for (int lit : c) {
                if (lit < 0) {
                    ++negs;
                    neg_var = -lit;
                } else if (lit > 0) {
                    pos.push_back(lit);
                } else {
                    all_vars = false;
                }
            }
            if (all_vars && negs == 1 && !pos.empty()) {
                std::sort(pos.begin(), pos.end());
                pos.erase(std::unique(pos.begin(), pos.end()), pos.end());
                long_support[neg_var] = pos;
            }
        }
    }

    PairStructure ps;
    for (const auto& p : pos_pairs) {
        if (!neg_pairs.count(p)) continue;
        int a = p.first, b = p.second;
        bool a_has = long_support.count(a);
        bool b_has = long_support.count(b);
        if (a_has == b_has) continue;
        int arc = a_has ? b : a;
        int comp = a_has ? a : b;
        ps.arc_set.insert(arc);
        ps.comp_of[arc] = comp;
    }
    ps.arcs.assign(ps.arc_set.begin(), ps.arc_set.end());
    std::sort(ps.arcs.begin(), ps.arcs.end());
    for (int arc : ps.arcs) {
        int comp = ps.comp_of[arc];
        auto it = long_support.find(comp);
        if (it == long_support.end()) continue;
        ps.support_of[arc] = sorted_intersection(it->second, ps.arc_set);
    }
    return ps;
}

static std::unordered_map<int, bool> unit_assignments(const Formula& f) {
    std::unordered_map<int, bool> units;
    for (const Clause& c : f.clauses) {
        if (c.size() == 1) units[std::abs(c[0])] = c[0] > 0;
    }
    return units;
}

static bool build_type_b_rows(const Formula& f, const PairStructure& ps, RowSystem& rs) {
    if (ps.arcs.empty()) return false;
    std::unordered_set<std::string> seen;
    for (size_t i = 0; i < f.clauses.size(); ++i) {
        const Clause& c = f.clauses[i];
        if (c.size() <= 1) continue;
        bool ok = true;
        std::vector<int> row;
        for (int lit : c) {
            if (lit <= 0 || !ps.arc_set.count(lit)) {
                ok = false;
                break;
            }
            row.push_back(lit);
        }
        if (!ok) continue;
        std::sort(row.begin(), row.end());
        row.erase(std::unique(row.begin(), row.end()), row.end());
        std::string key = key_of(row);
        if (seen.insert(key).second) {
            rs.rows.push_back(row);
            rs.row_clause_ids.push_back(static_cast<int>(i) + 1);
        }
    }
    return !rs.rows.empty();
}

static std::vector<std::pair<int, int>> positive_unit_runs(const Formula& f) {
    auto units = unit_assignments(f);
    std::vector<int> vars;
    for (const auto& kv : units) {
        if (kv.second && kv.first > f.nvars / 2) vars.push_back(kv.first);
    }
    std::sort(vars.begin(), vars.end());
    std::vector<std::pair<int, int>> runs;
    for (size_t i = 0; i < vars.size();) {
        size_t j = i;
        while (j + 1 < vars.size() && vars[j + 1] == vars[j] + 1) ++j;
        runs.push_back({vars[i], vars[j]});
        i = j + 1;
    }
    return runs;
}

static bool build_type_a_rows_for_interval(
    const Formula& f,
    const PairStructure& ps,
    int node_start,
    int node_count,
    RowSystem& rs
) {
    int node_end = node_start + node_count - 1;
    auto units = unit_assignments(f);
    auto is_node = [&](int v) { return node_start <= v && v <= node_end; };

    std::unordered_map<int, int> aux_gate_to_arc;
    std::vector<std::pair<int, int>> direct_rows; // node index, arc

    for (const Clause& c : f.clauses) {
        if (c.size() != 3) continue;
        int positives = 0, pos = 0;
        std::vector<int> neg;
        for (int lit : c) {
            if (lit > 0) {
                ++positives;
                pos = lit;
            } else {
                neg.push_back(-lit);
            }
        }
        if (positives != 1 || neg.size() != 2) continue;
        bool n0 = is_node(neg[0]);
        bool n1 = is_node(neg[1]);
        if (n0 == n1) continue;
        int arc = n0 ? neg[1] : neg[0];
        if (!ps.arc_set.count(arc)) continue;
        if (pos > node_end) {
            aux_gate_to_arc[pos] = arc;
        } else if (is_node(pos)) {
            direct_rows.push_back({pos - node_start, arc});
        }
    }

    std::vector<std::set<int>> row_sets(node_count);
    for (const Clause& c : f.clauses) {
        if (c.size() < 2) continue;
        int negs = 0;
        int target = 0;
        for (int lit : c) {
            if (lit < 0) {
                ++negs;
                target = -lit;
            }
        }
        if (negs != 1 || !is_node(target)) continue;
        for (int lit : c) {
            if (lit > node_end) {
                auto it = aux_gate_to_arc.find(lit);
                if (it != aux_gate_to_arc.end()) row_sets[target - node_start].insert(it->second);
            }
        }
    }
    for (const auto& pr : direct_rows) row_sets[pr.first].insert(pr.second);

    for (int i = 0; i < node_count; ++i) {
        if (row_sets[i].empty()) continue;
        int node_var = node_start + i;
        auto uit = units.find(node_var);
        if (uit == units.end() || !uit->second) return false;
        std::vector<int> row(row_sets[i].begin(), row_sets[i].end());
        rs.rows.push_back(row);
        rs.row_clause_ids.push_back(0);
    }
    return !rs.rows.empty();
}

static bool complete_columns(const PairStructure& ps, RowSystem& rs) {
    std::unordered_map<int, int> row_of_arc;
    for (size_t r = 0; r < rs.rows.size(); ++r) {
        for (int a : rs.rows[r]) {
            if (row_of_arc.count(a)) return false;
            row_of_arc[a] = static_cast<int>(r);
        }
    }
    if (row_of_arc.empty()) return false;
    std::unordered_set<int> row_arcs;
    for (const auto& kv : row_of_arc) row_arcs.insert(kv.first);

    std::unordered_map<std::string, int> col_index;
    rs.edges.assign(rs.rows.size(), {});
    for (const auto& kv : row_of_arc) {
        int arc = kv.first;
        int r = kv.second;
        auto sit = ps.support_of.find(arc);
        if (sit == ps.support_of.end()) return false;
        std::unordered_set<int> colset;
        colset.insert(arc);
        std::unordered_set<int> same_row(rs.rows[r].begin(), rs.rows[r].end());
        for (int s : sit->second) {
            if (!row_arcs.count(s)) continue;
            if (same_row.count(s) && s != arc) continue;
            colset.insert(s);
        }
        std::vector<int> col(colset.begin(), colset.end());
        std::sort(col.begin(), col.end());
        std::string key = key_of(col);
        auto it = col_index.find(key);
        int ci;
        if (it == col_index.end()) {
            ci = static_cast<int>(rs.cols.size());
            col_index[key] = ci;
            rs.cols.push_back(col);
        } else {
            ci = it->second;
        }
        rs.edges[r].push_back(ci);
    }
    for (auto& e : rs.edges) {
        std::sort(e.begin(), e.end());
        e.erase(std::unique(e.begin(), e.end()), e.end());
    }
    return !rs.cols.empty();
}

struct MatchingResult {
    int matching = 0;
    std::vector<int> pair_u;
    std::vector<int> pair_v;
    std::vector<int> hall_rows;
    std::vector<int> hall_cols;
};

static MatchingResult hopcroft_karp(const std::vector<std::vector<int>>& edges, int ncols) {
    int nrows = static_cast<int>(edges.size());
    MatchingResult res;
    res.pair_u.assign(nrows, -1);
    res.pair_v.assign(ncols, -1);
    std::vector<int> dist(nrows);
    auto bfs = [&]() {
        std::queue<int> q;
        bool found = false;
        for (int u = 0; u < nrows; ++u) {
            if (res.pair_u[u] < 0) {
                dist[u] = 0;
                q.push(u);
            } else {
                dist[u] = std::numeric_limits<int>::max();
            }
        }
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (int v : edges[u]) {
                int pu = res.pair_v[v];
                if (pu < 0) {
                    found = true;
                } else if (dist[pu] == std::numeric_limits<int>::max()) {
                    dist[pu] = dist[u] + 1;
                    q.push(pu);
                }
            }
        }
        return found;
    };
    std::function<bool(int)> dfs = [&](int u) {
        for (int v : edges[u]) {
            int pu = res.pair_v[v];
            if (pu < 0 || (dist[pu] == dist[u] + 1 && dfs(pu))) {
                res.pair_u[u] = v;
                res.pair_v[v] = u;
                return true;
            }
        }
        dist[u] = std::numeric_limits<int>::max();
        return false;
    };
    while (bfs()) {
        for (int u = 0; u < nrows; ++u) {
            if (res.pair_u[u] < 0 && dfs(u)) ++res.matching;
        }
    }

    std::vector<char> vis_u(nrows, 0), vis_v(ncols, 0);
    std::queue<int> q;
    for (int u = 0; u < nrows; ++u) {
        if (res.pair_u[u] < 0) {
            vis_u[u] = 1;
            q.push(u);
        }
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int v : edges[u]) {
            if (res.pair_u[u] == v) continue;
            if (vis_v[v]) continue;
            vis_v[v] = 1;
            int pu = res.pair_v[v];
            if (pu >= 0 && !vis_u[pu]) {
                vis_u[pu] = 1;
                q.push(pu);
            }
        }
    }
    for (int u = 0; u < nrows; ++u) {
        if (vis_u[u]) res.hall_rows.push_back(u);
    }
    for (int v = 0; v < ncols; ++v) {
        if (vis_v[v]) res.hall_cols.push_back(v);
    }
    return res;
}

static void write_rup_clause(std::ofstream& out, const std::vector<int>& lits) {
    out << "rup ";
    for (int lit : lits) out << "1 " << lit_name(lit) << ' ';
    out << ">= 1 ;\n";
}

static bool derive_amo(std::ofstream& out, const std::vector<int>& vars, int& next_id, int& amo_id) {
    int d = static_cast<int>(vars.size());
    if (d <= 0 || d > 18) return false;
    if (d == 1) {
        out << "pol ~x" << vars[0] << " ;\n";
        amo_id = next_id++;
        return true;
    }
    std::unordered_map<int, int> subset_id;
    for (int i = 0; i < d; ++i) {
        for (int j = i + 1; j < d; ++j) {
            out << "rup 1 ~x" << vars[i] << " 1 ~x" << vars[j] << " >= 1 ;\n";
            subset_id[(1 << i) | (1 << j)] = next_id++;
        }
    }
    std::vector<int> comb;
    std::function<void(int, int, int)> gen = [&](int start, int left, int k) {
        if (left == 0) {
            int mask = 0;
            for (int idx : comb) mask |= (1 << idx);
            out << "pol ";
            bool first = true;
            for (int idx : comb) {
                int sub = mask ^ (1 << idx);
                auto it = subset_id.find(sub);
                if (it == subset_id.end()) {
                    // Should not happen for increasing k.
                    out << "0 ";
                } else {
                    out << it->second << ' ';
                }
                if (!first) out << "+ ";
                first = false;
            }
            out << (k - 1) << " d ;\n";
            subset_id[mask] = next_id++;
            return;
        }
        for (int i = start; i <= d - left; ++i) {
            comb.push_back(i);
            gen(i + 1, left - 1, k);
            comb.pop_back();
        }
    };
    for (int k = 3; k <= d; ++k) gen(0, k, k);
    amo_id = subset_id[(1 << d) - 1];
    return true;
}

static bool write_hall_proof(const Formula& f, const RowSystem& rs, const std::string& proof_path) {
    if (rs.rows.empty() || rs.cols.empty()) return false;
    MatchingResult mr = hopcroft_karp(rs.edges, static_cast<int>(rs.cols.size()));
    if (mr.matching == static_cast<int>(rs.rows.size())) return false;
    if (mr.hall_rows.empty() || mr.hall_rows.size() <= mr.hall_cols.size()) return false;

    std::unordered_set<int> hall_row_set(mr.hall_rows.begin(), mr.hall_rows.end());
    std::unordered_set<int> hall_col_set(mr.hall_cols.begin(), mr.hall_cols.end());
    std::unordered_set<int> hall_arcs;
    for (int r : mr.hall_rows) {
        for (int a : rs.rows[r]) hall_arcs.insert(a);
    }
    for (int r : mr.hall_rows) {
        for (int c : rs.edges[r]) {
            if (!hall_col_set.count(c)) return false;
        }
    }

    std::ofstream out(proof_path);
    if (!out) return false;
    out << "pseudo-Boolean proof version 3.0\n";
    out << "f " << f.clauses.size() << " ;\n";
    int next_id = static_cast<int>(f.clauses.size()) + 1;
    std::vector<int> row_ids;
    for (int r : mr.hall_rows) {
        if (rs.row_clause_ids[r] > 0) {
            row_ids.push_back(rs.row_clause_ids[r]);
        } else {
            std::vector<int> lits = rs.rows[r];
            std::sort(lits.begin(), lits.end());
            write_rup_clause(out, lits);
            row_ids.push_back(next_id++);
        }
    }

    std::vector<int> amo_ids;
    for (int c : mr.hall_cols) {
        std::vector<int> subset;
        for (int a : rs.cols[c]) {
            if (hall_arcs.count(a)) subset.push_back(a);
        }
        std::sort(subset.begin(), subset.end());
        subset.erase(std::unique(subset.begin(), subset.end()), subset.end());
        if (subset.empty()) return false;
        int id = 0;
        if (!derive_amo(out, subset, next_id, id)) return false;
        amo_ids.push_back(id);
    }

    out << "pol ";
    bool first = true;
    for (int id : row_ids) {
        out << id << ' ';
        if (!first) out << "+ ";
        first = false;
    }
    for (int id : amo_ids) {
        out << id << ' ';
        if (!first) out << "+ ";
        first = false;
    }
    out << ";\n";
    int contradiction_id = next_id++;
    out << "output NONE ;\n";
    out << "conclusion UNSAT : " << contradiction_id << " ;\n";
    out << "end pseudo-Boolean proof ;\n";
    return true;
}

static bool try_type_b(const Formula& f, const PairStructure& ps, const std::string& proof_path) {
    RowSystem rs;
    if (!build_type_b_rows(f, ps, rs)) return false;
    if (!complete_columns(ps, rs)) return false;
    return write_hall_proof(f, rs, proof_path);
}

static bool try_type_a(const Formula& f, const PairStructure& ps, const std::string& proof_path) {
    auto runs = positive_unit_runs(f);
    std::vector<std::pair<int, int>> candidates;
    for (auto [a, b] : runs) {
        int len = b - a + 1;
        if ((len == 60 || len == 50) && a > f.nvars / 2) candidates.push_back({a, len});
    }
    std::sort(candidates.begin(), candidates.end(), [](auto x, auto y) {
        if (x.second != y.second) return x.second > y.second;
        return x.first > y.first;
    });
    for (auto [start, count] : candidates) {
        RowSystem rs;
        if (!build_type_a_rows_for_interval(f, ps, start, count, rs)) continue;
        if (!complete_columns(ps, rs)) continue;
        if (write_hall_proof(f, rs, proof_path)) return true;
    }
    return false;
}

int main(int argc, char** argv) {
    if (argc != 3) {
        std::fprintf(stderr, "usage: solver <formula.cnf> <out.pbp>\n");
        return 1;
    }
    Formula f;
    if (!read_dimacs(argv[1], f)) {
        std::printf("s UNKNOWN\n");
        return 0;
    }
    PairStructure ps = parse_pairs(f);
    bool proved = false;
    if (!ps.arcs.empty()) {
        proved = try_type_b(f, ps, argv[2]);
        if (!proved) proved = try_type_a(f, ps, argv[2]);
    }
    if (proved) {
        std::printf("s UNSATISFIABLE\n");
        return 20;
    }
    std::printf("s UNKNOWN\n");
    return 0;
}
