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

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

struct Clause {
    vector<int> lits;
};

struct DSU {
    vector<int> p;
    explicit DSU(int n = 0) : p(n) { std::iota(p.begin(), p.end(), 0); }
    int find(int x) {
        while (p[x] != x) {
            p[x] = p[p[x]];
            x = p[x];
        }
        return x;
    }
    void unite(int a, int b) {
        a = find(a);
        b = find(b);
        if (a != b) p[b] = a;
    }
};

static uint64_t key_pair(int a, int b) {
    if (a > b) std::swap(a, b);
    return (uint64_t(uint32_t(a)) << 32) | uint32_t(b);
}

static bool parse_dimacs(const string& path, int& nvars, int& nclauses, vector<Clause>& clauses) {
    std::ifstream in(path);
    if (!in) return false;
    string tok;
    nvars = nclauses = 0;
    while (in >> tok) {
        if (tok == "c") {
            string rest;
            std::getline(in, rest);
        } else if (tok == "p") {
            string fmt;
            in >> fmt >> nvars >> nclauses;
            if (fmt != "cnf" || nvars <= 0 || nclauses < 0) return false;
        } else if (!tok.empty() && tok[0] == '%') {
            break;
        } else {
            Clause c;
            int lit = std::stoi(tok);
            while (lit != 0) {
                if (lit == 0 || std::abs(lit) > nvars) return false;
                c.lits.push_back(lit);
                if (!(in >> lit)) return false;
            }
            if (!c.lits.empty()) clauses.push_back(std::move(c));
        }
    }
    return nvars > 0 && (nclauses == 0 || int(clauses.size()) == nclauses);
}

static bool write_unknown() {
    std::cout << "s UNKNOWN\n";
    return true;
}

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

    int nvars = 0, declared_clauses = 0;
    vector<Clause> clauses;
    if (!parse_dimacs(argv[1], nvars, declared_clauses, clauses)) {
        std::cout << "c parse failed\n";
        write_unknown();
        return 0;
    }
    const int m = int(clauses.size());

    vector<int> slot_clause_ids;
    vector<vector<int>> slot_vars;
    for (int i = 0; i < m; ++i) {
        const auto& c = clauses[i].lits;
        if (c.size() > 2 && std::all_of(c.begin(), c.end(), [](int x) { return x > 0; })) {
            vector<int> s = c;
            std::sort(s.begin(), s.end());
            if (std::adjacent_find(s.begin(), s.end()) != s.end()) {
                write_unknown();
                return 0;
            }
            slot_clause_ids.push_back(i + 1);
            slot_vars.push_back(std::move(s));
        }
    }
    if (slot_vars.size() < 2) {
        std::cout << "c no positive slot clauses detected\n";
        write_unknown();
        return 0;
    }
    const int K = int(slot_vars.size());
    const int V = int(slot_vars[0].size());
    if (V <= 0) {
        write_unknown();
        return 0;
    }
    for (const auto& s : slot_vars) {
        if (int(s.size()) != V) {
            std::cout << "c unequal slot sizes\n";
            write_unknown();
            return 0;
        }
    }

    vector<int> var_slot(nvars + 1, -1), var_vertex(nvars + 1, -1);
    for (int p = 0; p < K; ++p) {
        for (int v = 0; v < V; ++v) {
            int x = slot_vars[p][v];
            if (x < 1 || x > nvars || var_slot[x] != -1) {
                std::cout << "c slot clauses do not partition variables\n";
                write_unknown();
                return 0;
            }
            var_slot[x] = p;
            var_vertex[x] = v;
        }
    }
    for (int x = 1; x <= nvars; ++x) {
        if (var_slot[x] == -1) {
            std::cout << "c variables outside slot partition\n";
            write_unknown();
            return 0;
        }
    }

    std::unordered_set<uint64_t> binary_neg;
    binary_neg.reserve(size_t(m) * 2);
    for (const auto& cl : clauses) {
        const auto& c = cl.lits;
        if (c.size() == 2 && c[0] < 0 && c[1] < 0) {
            int a = -c[0], b = -c[1];
            if (a == b) {
                write_unknown();
                return 0;
            }
            binary_neg.insert(key_pair(a, b));
        } else if (!(c.size() > 2 && std::all_of(c.begin(), c.end(), [](int x) { return x > 0; }))) {
            std::cout << "c unsupported clause shape\n";
            write_unknown();
            return 0;
        }
    }

    DSU dsu(V);
    for (const auto& cl : clauses) {
        const auto& c = cl.lits;
        if (c.size() != 2 || c[0] >= 0 || c[1] >= 0) continue;
        int a = -c[0], b = -c[1];
        int pa = var_slot[a], pb = var_slot[b];
        int va = var_vertex[a], vb = var_vertex[b];
        if (pa != pb && va != vb) dsu.unite(va, vb);
    }

    vector<vector<int>> groups_tmp(V);
    for (int v = 0; v < V; ++v) groups_tmp[dsu.find(v)].push_back(v);
    vector<vector<int>> groups;
    for (auto& g : groups_tmp) {
        if (!g.empty()) {
            std::sort(g.begin(), g.end());
            groups.push_back(g);
        }
    }
    std::sort(groups.begin(), groups.end(), [](const auto& a, const auto& b) { return a[0] < b[0]; });
    const int G = int(groups.size());
    if (G <= 0 || K <= G) {
        std::cout << "c detected " << K << " slots and " << G << " groups; no pigeonhole contradiction\n";
        write_unknown();
        return 0;
    }

    vector<int> vertex_group(V, -1);
    for (int g = 0; g < G; ++g)
        for (int v : groups[g]) vertex_group[v] = g;

    // The proof below needs every pair of variables in a recovered group to be
    // explicitly forbidden by a binary negative clause.
    for (int g = 0; g < G; ++g) {
        vector<int> vars;
        for (int p = 0; p < K; ++p)
            for (int v : groups[g]) vars.push_back(slot_vars[p][v]);
        for (size_t i = 0; i < vars.size(); ++i) {
            for (size_t j = i + 1; j < vars.size(); ++j) {
                if (!binary_neg.count(key_pair(vars[i], vars[j]))) {
                    std::cout << "c missing binary support inside group\n";
                    write_unknown();
                    return 0;
                }
            }
        }
    }

    std::ofstream proof(argv[2]);
    if (!proof) {
        std::fprintf(stderr, "could not open proof output\n");
        return 1;
    }
    proof << "pseudo-Boolean proof version 3.0\n";
    proof << "f " << m << " ;\n";

    int cur_id = m;
    vector<int> group_amo_ids;
    for (const auto& group : groups) {
        vector<int> vars;
        for (int p = 0; p < K; ++p)
            for (int v : group) vars.push_back(slot_vars[p][v]);

        proof << "pbc";
        for (int x : vars) proof << " 1 ~x" << x;
        proof << " >= " << (int(vars.size()) - 1) << " : subproof\n";
        ++cur_id; // the negated goal premise in the subproof
        for (int x : vars) {
            proof << "rup 1 ~x" << x << " >= 1 ;\n";
            ++cur_id;
        }
        proof << "rup >= 1 ;\n";
        ++cur_id;
        proof << "qed pbc : -1 ;\n";
        ++cur_id;
        group_amo_ids.push_back(cur_id);
    }

    vector<int> sum_ids = slot_clause_ids;
    sum_ids.insert(sum_ids.end(), group_amo_ids.begin(), group_amo_ids.end());
    proof << "pol " << sum_ids[0];
    for (size_t i = 1; i < sum_ids.size(); ++i) proof << " " << sum_ids[i] << " +";
    proof << " ;\n";
    ++cur_id;
    proof << "output NONE ;\n";
    proof << "conclusion UNSAT : " << cur_id << " ;\n";
    proof << "end pseudo-Boolean proof ;\n";
    proof.close();

    std::cout << "c detected partitioned clique formula: slots=" << K
              << " vertices_per_slot=" << V << " groups=" << G << "\n";
    std::cout << "s UNSATISFIABLE\n";
    return 20;
}
