#include <algorithm>
#include <cctype>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <numeric>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

using namespace std;

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

static bool parse_dimacs(const string& path, Formula& f) {
    ifstream in(path);
    if (!in) return false;
    string line;
    vector<int> cur;
    while (getline(in, line)) {
        if (line.empty()) continue;
        size_t p = 0;
        while (p < line.size() && isspace(static_cast<unsigned char>(line[p]))) ++p;
        if (p == line.size()) continue;
        if (line[p] == 'c') continue;
        if (line[p] == 'p') {
            string tmp, typ;
            istringstream iss(line.substr(p));
            iss >> tmp >> typ >> f.vars >> f.declared_clauses;
            if (typ != "cnf" || f.vars <= 0 || f.declared_clauses < 0) return false;
            continue;
        }
        istringstream iss(line.substr(p));
        int lit;
        while (iss >> lit) {
            if (lit == 0) {
                f.clauses.push_back(cur);
                cur.clear();
            } else {
                if (abs(lit) > f.vars) return false;
                cur.push_back(lit);
            }
        }
    }
    if (!cur.empty()) return false;
    return f.vars > 0 && (f.declared_clauses == 0 || (int)f.clauses.size() == f.declared_clauses);
}

struct Instance {
    int k = 0;
    vector<vector<int>> group_vars;
    vector<pair<int,int>> var_to_gc;
    vector<vector<uint64_t>> edge_colors;
    vector<vector<int>> adj;
};

static bool detect_register_instance(const Formula& f, Instance& inst) {
    int k = -1;
    vector<vector<int>> groups;
    for (const auto& cl : f.clauses) {
        bool pos = cl.size() > 2;
        for (int lit : cl) pos = pos && lit > 0;
        if (!pos) continue;
        if (k < 0) k = (int)cl.size();
        if ((int)cl.size() == k) groups.push_back(cl);
    }
    if (k < 2 || k > 30 || groups.empty()) return false;
    if ((int)groups.size() * k != f.vars) return false;

    vector<pair<int,int>> var_to_gc(f.vars + 1, {-1, -1});
    vector<char> seen(f.vars + 1, 0);
    for (int g = 0; g < (int)groups.size(); ++g) {
        if ((int)groups[g].size() != k) return false;
        for (int c = 0; c < k; ++c) {
            int v = groups[g][c];
            if (v <= 0 || v > f.vars || seen[v]) return false;
            seen[v] = 1;
            var_to_gc[v] = {g, c};
        }
    }
    for (int v = 1; v <= f.vars; ++v) {
        if (!seen[v]) return false;
    }

    int n = (int)groups.size();
    vector<vector<uint64_t>> edge_colors(n, vector<uint64_t>(n, 0));
    uint64_t full = (k == 64 ? ~0ULL : ((1ULL << k) - 1ULL));
    for (const auto& cl : f.clauses) {
        bool pos_domain = (int)cl.size() == k;
        for (int lit : cl) pos_domain = pos_domain && lit > 0;
        if (pos_domain) continue;

        if (cl.size() != 2 || cl[0] >= 0 || cl[1] >= 0) return false;
        int a = -cl[0], b = -cl[1];
        auto [ga, ca] = var_to_gc[a];
        auto [gb, cb] = var_to_gc[b];
        if (ga < 0 || gb < 0 || ga == gb || ca != cb) return false;
        if (ga > gb) swap(ga, gb);
        edge_colors[ga][gb] |= (1ULL << ca);
    }

    vector<vector<int>> adj(n);
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (edge_colors[i][j] == full) {
                adj[i].push_back(j);
                adj[j].push_back(i);
            }
        }
    }
    inst.k = k;
    inst.group_vars = std::move(groups);
    inst.var_to_gc = std::move(var_to_gc);
    inst.edge_colors = std::move(edge_colors);
    inst.adj = std::move(adj);
    return true;
}

static bool adjacent(const vector<vector<char>>& mat, int a, int b) {
    return mat[a][b] != 0;
}

static bool clique_dfs(const vector<vector<char>>& mat, int target, vector<int>& chosen, vector<int> cand) {
    if ((int)chosen.size() == target) return true;
    if ((int)chosen.size() + (int)cand.size() < target) return false;

    sort(cand.begin(), cand.end(), [&](int a, int b) {
        int da = accumulate(mat[a].begin(), mat[a].end(), 0);
        int db = accumulate(mat[b].begin(), mat[b].end(), 0);
        return da > db;
    });

    while (!cand.empty()) {
        if ((int)chosen.size() + (int)cand.size() < target) return false;
        int v = cand.front();
        cand.erase(cand.begin());
        vector<int> next;
        next.reserve(cand.size());
        for (int u : cand) {
            if (adjacent(mat, v, u)) next.push_back(u);
        }
        chosen.push_back(v);
        if (clique_dfs(mat, target, chosen, std::move(next))) return true;
        chosen.pop_back();
    }
    return false;
}

static vector<int> find_clique(const Instance& inst) {
    int n = (int)inst.group_vars.size();
    vector<vector<char>> mat(n, vector<char>(n, 0));
    for (int i = 0; i < n; ++i) {
        for (int j : inst.adj[i]) mat[i][j] = 1;
    }
    vector<int> cand(n), chosen;
    iota(cand.begin(), cand.end(), 0);
    if (clique_dfs(mat, inst.k + 1, chosen, std::move(cand))) return chosen;
    return {};
}

static void emit_pos_terms(ostream& out, const Instance& inst, const vector<int>& clique, int mask) {
    int k = inst.k;
    int p = __builtin_popcount((unsigned)mask);
    bool first = true;
    for (int i = 0; i <= p; ++i) {
        int g = clique[i];
        for (int c = 0; c < k; ++c) {
            if ((mask >> c) & 1) continue;
            if (!first) out << ' ';
            first = false;
            out << "1 x" << inst.group_vars[g][c];
        }
    }
}

static bool mask_has_no_terms(const Instance& inst, int mask) {
    return mask == ((1 << inst.k) - 1);
}

static bool write_hall_proof(const string& path, const Instance& inst, const vector<int>& clique) {
    ofstream out(path);
    if (!out) return false;
    int k = inst.k;
    int full = (1 << k) - 1;

    out << "pseudo-Boolean proof version 3.0\n";
    for (int sz = 1; sz <= k; ++sz) {
        for (int mask = 1; mask <= full; ++mask) {
            if (__builtin_popcount((unsigned)mask) != sz) continue;
            int last_pos = sz;
            int last_group = clique[last_pos];
            for (int c = 0; c < k; ++c) {
                if (((mask >> c) & 1) == 0) continue;
                out << "rup ";
                bool empty_h = mask_has_no_terms(inst, mask);
                if (!empty_h) {
                    emit_pos_terms(out, inst, clique, mask);
                    out << ' ';
                }
                out << "1 ~x" << inst.group_vars[last_group][c] << " >= 1 ;\n";
            }
            if (mask_has_no_terms(inst, mask)) {
                out << "rup >= 1 ;\n";
            } else {
                out << "rup ";
                emit_pos_terms(out, inst, clique, mask);
                out << " >= 1 ;\n";
            }
        }
    }
    out << "output NONE;\n";
    out << "conclusion UNSAT : -1;\n";
    out << "end pseudo-Boolean proof;\n";
    return (bool)out;
}

int main(int argc, char** argv) {
    if (argc != 3) {
        cerr << "usage: solver <formula.cnf> <out.pbp>\n";
        return 1;
    }
    Formula f;
    if (!parse_dimacs(argv[1], f)) {
        cout << "c parse failed\ns UNKNOWN\n";
        return 0;
    }
    Instance inst;
    if (!detect_register_instance(f, inst)) {
        cout << "c unsupported CNF shape\ns UNKNOWN\n";
        return 0;
    }
    vector<int> clique = find_clique(inst);
    if ((int)clique.size() != inst.k + 1) {
        cout << "c no register clique of size k+1 found; k=" << inst.k
             << " objects=" << inst.group_vars.size() << "\n";
        cout << "s UNKNOWN\n";
        return 0;
    }
    if (!write_hall_proof(argv[2], inst, clique)) {
        cout << "c failed to write proof\ns UNKNOWN\n";
        return 0;
    }
    cout << "c register-allocation coloring instance: k=" << inst.k
         << " objects=" << inst.group_vars.size()
         << " clique=" << clique.size() << "\n";
    cout << "s UNSATISFIABLE\n";
    return 20;
}
