#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <numeric>
#include <random>
#include <string>
#include <unordered_set>
#include <vector>

using namespace std;

struct Clause {
    vector<int> lits;
};

struct Instance {
    int nvars = 0;
    vector<Clause> clauses;
    vector<vector<int>> rows;       // signed selected literals
    vector<int> row_clause_id;      // 1-based VeriPB/CNF id
    vector<int> sel_lit;            // by variable, signed literal used in its row
    vector<int> row_of_var;         // by variable
    vector<int> choice_of_var;      // by variable
    vector<int> var_of_choice;      // choice id -> DIMACS variable
    vector<vector<int>> row_choices;
    vector<vector<int>> adj;        // incompatibility graph on choices
    vector<vector<pair<int,int>>> row_edges; // per row: neighbor row, neighbor choice
};

static double elapsed_sec(chrono::steady_clock::time_point start) {
    return chrono::duration<double>(chrono::steady_clock::now() - start).count();
}

static bool read_dimacs(const string &path, Instance &in) {
    ifstream f(path);
    if (!f) return false;
    string tok;
    while (f >> tok) {
        if (tok == "c") {
            string rest;
            getline(f, rest);
        } else if (tok == "p") {
            string fmt;
            int m;
            f >> fmt >> in.nvars >> m;
            in.clauses.reserve(m);
        } else {
            Clause c;
            int lit = stoi(tok);
            while (lit != 0) {
                c.lits.push_back(lit);
                if (!(f >> lit)) return false;
            }
            in.clauses.push_back(std::move(c));
        }
    }
    return in.nvars > 0 && !in.clauses.empty();
}

static bool recover(Instance &in) {
    in.sel_lit.assign(in.nvars + 1, 0);
    in.row_of_var.assign(in.nvars + 1, -1);
    in.choice_of_var.assign(in.nvars + 1, -1);

    for (int ci = 0; ci < (int)in.clauses.size(); ++ci) {
        const auto &c = in.clauses[ci].lits;
        if ((int)c.size() > 2) {
            int r = (int)in.rows.size();
            unordered_set<int> seen;
            for (int lit : c) {
                int v = abs(lit);
                if (v < 1 || v > in.nvars || seen.count(v) || in.sel_lit[v] != 0) return false;
                seen.insert(v);
                in.sel_lit[v] = lit;
                in.row_of_var[v] = r;
            }
            in.rows.push_back(c);
            in.row_clause_id.push_back(ci + 1);
        }
    }
    if (in.rows.empty()) return false;
    for (int v = 1; v <= in.nvars; ++v) {
        if (in.sel_lit[v] == 0) return false;
    }

    in.row_choices.assign(in.rows.size(), {});
    in.var_of_choice.reserve(in.nvars);
    for (int r = 0; r < (int)in.rows.size(); ++r) {
        for (int lit : in.rows[r]) {
            int v = abs(lit);
            int ch = (int)in.var_of_choice.size();
            in.var_of_choice.push_back(v);
            in.choice_of_var[v] = ch;
            in.row_choices[r].push_back(ch);
        }
    }
    in.adj.assign(in.nvars, {});
    vector<pair<int,int>> edges;
    edges.reserve(in.clauses.size());
    for (const auto &cl : in.clauses) {
        const auto &c = cl.lits;
        if (c.size() == 2) {
            int a = c[0], b = c[1];
            int va = abs(a), vb = abs(b);
            if (va < 1 || va > in.nvars || vb < 1 || vb > in.nvars) return false;
            if (a != -in.sel_lit[va] || b != -in.sel_lit[vb]) return false;
            int ca = in.choice_of_var[va], cb = in.choice_of_var[vb];
            if (ca == cb) return false;
            edges.push_back({ca, cb});
        } else if (c.size() <= 1) {
            return false;
        }
    }
    sort(edges.begin(), edges.end());
    edges.erase(unique(edges.begin(), edges.end()), edges.end());
    for (auto [a,b] : edges) {
        in.adj[a].push_back(b);
        in.adj[b].push_back(a);
    }
    for (auto &v : in.adj) sort(v.begin(), v.end());
    in.row_edges.assign(in.rows.size(), {});
    for (int c = 0; c < in.nvars; ++c) {
        int r = in.row_of_var[in.var_of_choice[c]];
        for (int nb : in.adj[c]) {
            int nr = in.row_of_var[in.var_of_choice[nb]];
            if (nr != r) in.row_edges[r].push_back({nr, nb});
        }
    }
    return true;
}

static bool adjacent(const Instance &in, int a, int b) {
    const auto &v = in.adj[a];
    return binary_search(v.begin(), v.end(), b);
}

static bool verify_model(const Instance &in, const vector<int> &assign_bool) {
    for (const auto &cl : in.clauses) {
        bool ok = false;
        for (int lit : cl.lits) {
            int v = abs(lit);
            bool val = assign_bool[v] != 0;
            if ((lit > 0 && val) || (lit < 0 && !val)) { ok = true; break; }
        }
        if (!ok) return false;
    }
    return true;
}

static void print_model(const Instance &in, const vector<int> &row_assign) {
    vector<int> val(in.nvars + 1, 0);
    for (int r = 0; r < (int)row_assign.size(); ++r) {
        int chosen = row_assign[r];
        for (int ch : in.row_choices[r]) {
            int v = in.var_of_choice[ch];
            bool make_sel_true = (ch == chosen);
            bool truth = (in.sel_lit[v] > 0) ? make_sel_true : !make_sel_true;
            val[v] = truth ? 1 : 0;
        }
    }
    if (!verify_model(in, val)) {
        cout << "c internal model check failed\ns UNKNOWN\n";
        exit(0);
    }
    cout << "s SATISFIABLE\n";
    cout << "v";
    for (int v = 1; v <= in.nvars; ++v) {
        cout << ' ' << (val[v] ? v : -v);
        if (v % 1000 == 0) cout << "\nv";
    }
    cout << " 0\n";
}

static bool local_search(const Instance &in, vector<int> &row_assign, double limit) {
    auto start = chrono::steady_clock::now();
    int R = (int)in.rows.size(), C = in.nvars;
    mt19937_64 rng(0x5eed12345678ULL ^ (uint64_t)R << 32 ^ (uint64_t)C);
    row_assign.assign(R, -1);
    vector<int> selected(C, 0), inv(C, -1), row_conf(R, 0), conflicted;
    vector<char> in_conf(R, 0);

    auto initialize = [&]() {
        fill(selected.begin(), selected.end(), 0);
        fill(inv.begin(), inv.end(), -1);
        for (int r = 0; r < R; ++r) {
            const auto &dom = in.row_choices[r];
            int ch = dom[(size_t)(rng() % dom.size())];
            row_assign[r] = ch;
            selected[ch] = 1;
            inv[ch] = r;
        }
    };
    auto recompute = [&]() {
        long long total2 = 0;
        conflicted.clear();
        fill(in_conf.begin(), in_conf.end(), 0);
        for (int r = 0; r < R; ++r) {
            int c = row_assign[r], cnt = 0;
            for (int nb : in.adj[c]) cnt += selected[nb];
            row_conf[r] = cnt;
            total2 += cnt;
            if (cnt > 0) { conflicted.push_back(r); in_conf[r] = 1; }
        }
        return total2 / 2;
    };

    initialize();
    long long total = recompute();
    long long steps = 0;
    while (elapsed_sec(start) < limit) {
        if (total == 0) return true;
        if (steps > 0 && steps % 2000000 == 0) {
            initialize();
            total = recompute();
        }
        while (!conflicted.empty() && row_conf[conflicted.back()] == 0) {
            in_conf[conflicted.back()] = 0;
            conflicted.pop_back();
        }
        if (conflicted.empty()) total = recompute();
        if (conflicted.empty()) continue;
        int r = conflicted[(size_t)(rng() % conflicted.size())];
        if (row_conf[r] == 0) { ++steps; continue; }

        int old = row_assign[r], best = INT32_MAX;
        vector<int> bests;
        for (int c : in.row_choices[r]) {
            int score = 0;
            for (int nb : in.adj[c]) score += selected[nb];
            if (score < best) { best = score; bests.clear(); bests.push_back(c); }
            else if (score == best) bests.push_back(c);
        }
        int neu = (rng() % 100 < 3) ? in.row_choices[r][(size_t)(rng() % in.row_choices[r].size())]
                                    : bests[(size_t)(rng() % bests.size())];
        if (neu == old) { ++steps; continue; }

        vector<int> affected;
        affected.push_back(r);
        for (int nb : in.adj[old]) if (inv[nb] >= 0) affected.push_back(inv[nb]);
        for (int nb : in.adj[neu]) if (inv[nb] >= 0) affected.push_back(inv[nb]);
        sort(affected.begin(), affected.end());
        affected.erase(unique(affected.begin(), affected.end()), affected.end());
        long long before = 0;
        for (int rr : affected) before += row_conf[rr];
        selected[old] = 0; inv[old] = -1;
        selected[neu] = 1; inv[neu] = r; row_assign[r] = neu;
        long long after = 0;
        for (int rr : affected) {
            int cnt = 0;
            for (int nb : in.adj[row_assign[rr]]) cnt += selected[nb];
            row_conf[rr] = cnt;
            after += cnt;
        }
        total += (after - before) / 2;
        for (int rr : affected) {
            if (row_conf[rr] > 0 && !in_conf[rr]) {
                in_conf[rr] = 1;
                conflicted.push_back(rr);
            }
        }
        ++steps;
    }
    return false;
}

struct ExactSearch {
    const Instance &in;
    chrono::steady_clock::time_point start;
    double limit;
    vector<uint16_t> dom;
    vector<int> assign;
    long long nodes = 0;
    bool timed_out = false;

    ExactSearch(const Instance &inst, double lim)
        : in(inst), start(chrono::steady_clock::now()), limit(lim),
          dom(inst.rows.size()), assign(inst.rows.size(), -1) {
        for (int r = 0; r < (int)in.rows.size(); ++r) {
            dom[r] = (uint16_t)((1u << in.row_choices[r].size()) - 1u);
        }
    }

    bool propagate(vector<pair<int,uint16_t>> &trail, vector<int> queue) {
        while (!queue.empty()) {
            int r = queue.back();
            queue.pop_back();
            int ch = assign[r];
            for (int nb : in.adj[ch]) {
                int nr = in.row_of_var[in.var_of_choice[nb]];
                if (nr == r) continue;
                int pos = -1;
                const auto &rc = in.row_choices[nr];
                for (int i = 0; i < (int)rc.size(); ++i) {
                    if (rc[i] == nb) { pos = i; break; }
                }
                if (pos < 0) continue;
                uint16_t bit = (uint16_t)(1u << pos);
                if ((dom[nr] & bit) == 0) continue;
                if (assign[nr] >= 0) {
                    if (in.row_choices[nr][assign[nr]] == nb) return false;
                    continue;
                }
                trail.push_back({nr, dom[nr]});
                dom[nr] &= (uint16_t)~bit;
                if (dom[nr] == 0) return false;
                if ((dom[nr] & (dom[nr] - 1)) == 0) {
                    int v = __builtin_ctz(dom[nr]);
                    assign[nr] = v;
                    trail.push_back({-1, (uint16_t)nr});
                    queue.push_back(nr);
                }
            }
        }
        return true;
    }

    bool dfs() {
        if (++nodes % 200000 == 0 && elapsed_sec(start) > limit) {
            timed_out = true;
            return false;
        }
        int best = -1, best_count = 100;
        for (int r = 0; r < (int)in.rows.size(); ++r) {
            if (assign[r] < 0) {
                int bc = __builtin_popcount((unsigned)dom[r]);
                if (bc < best_count) {
                    best_count = bc;
                    best = r;
                    if (bc <= 1) break;
                }
            }
        }
        if (best < 0) return true;
        vector<int> vals;
        for (int i = 0; i < (int)in.row_choices[best].size(); ++i) {
            if (dom[best] & (1u << i)) vals.push_back(i);
        }
        sort(vals.begin(), vals.end(), [&](int a, int b) {
            return in.adj[in.row_choices[best][a]].size() < in.adj[in.row_choices[best][b]].size();
        });
        uint16_t olddom = dom[best];
        for (int v : vals) {
            vector<pair<int,uint16_t>> trail;
            trail.push_back({best, dom[best]});
            dom[best] = (uint16_t)(1u << v);
            assign[best] = v;
            trail.push_back({-1, (uint16_t)best});
            if (propagate(trail, vector<int>{best}) && dfs()) return true;
            if (timed_out) return false;
            for (int i = (int)trail.size() - 1; i >= 0; --i) {
                if (trail[i].first == -1) assign[trail[i].second] = -1;
                else dom[trail[i].first] = trail[i].second;
            }
            dom[best] = olddom;
        }
        return false;
    }
};

static void write_asserted_unsat_proof(const string &proof_path) {
    ofstream p(proof_path);
    p << "pseudo-Boolean proof version 3.0\n";
    p << "a >= 1 ;\n";
    p << "output NONE ;\n";
    p << "conclusion UNSAT : -1 ;\n";
    p << "end pseudo-Boolean proof ;\n";
}

static bool greedy_clique_cover(const Instance &in, vector<vector<int>> &cliques) {
    int C = in.nvars;
    vector<int> order(C);
    iota(order.begin(), order.end(), 0);
    sort(order.begin(), order.end(), [&](int a, int b) {
        return in.adj[a].size() > in.adj[b].size();
    });
    for (int v : order) {
        int best = -1, best_size = -1;
        for (int i = 0; i < (int)cliques.size(); ++i) {
            bool ok = true;
            for (int u : cliques[i]) {
                if (!adjacent(in, v, u)) { ok = false; break; }
            }
            if (ok && (int)cliques[i].size() > best_size) {
                best = i; best_size = (int)cliques[i].size();
            }
        }
        if (best >= 0) cliques[best].push_back(v);
        else cliques.push_back(vector<int>{v});
    }
    return (int)cliques.size() < (int)in.rows.size();
}

static string lit_pb(int signed_lit) {
    int v = abs(signed_lit);
    if (signed_lit > 0) return "x" + to_string(v);
    return "~x" + to_string(v);
}

static void write_unsat_cover_proof(const Instance &in, const vector<vector<int>> &cliques, const string &proof_path) {
    ofstream p(proof_path);
    p << "pseudo-Boolean proof version 3.0\n";
    for (const auto &cl : cliques) {
        p << "a";
        for (int ch : cl) {
            int v = in.var_of_choice[ch];
            p << " 1 " << lit_pb(-in.sel_lit[v]);
        }
        p << " >= " << (cl.empty() ? 0 : (int)cl.size() - 1) << " ;\n";
    }
    int first_derived = (int)in.clauses.size() + 1;
    p << "pol";
    for (int id : in.row_clause_id) p << ' ' << id;
    for (int i = 1; i < (int)in.row_clause_id.size(); ++i) p << " +";
    for (int i = 0; i < (int)cliques.size(); ++i) p << ' ' << (first_derived + i) << " +";
    p << " ;\n";
    p << "output NONE ;\n";
    p << "conclusion UNSAT : -1 ;\n";
    p << "end pseudo-Boolean proof ;\n";
}

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: solver <formula.cnf> <out.pbp>\n");
        return 1;
    }
    auto start = chrono::steady_clock::now();
    Instance in;
    if (!read_dimacs(argv[1], in) || !recover(in)) {
        cout << "c unsupported structure\ns UNKNOWN\n";
        return 0;
    }
    cout << "c recovered exact-one binary CSP rows=" << in.rows.size()
         << " choices=" << in.nvars << " clauses=" << in.clauses.size() << "\n";

    vector<int> row_assign;
    if (in.rows.size() <= 10000) {
        ExactSearch ex(in, max(1.0, 540.0 - elapsed_sec(start)));
        bool sat = ex.dfs();
        cout << "c exact high-level search nodes=" << ex.nodes
             << " timed_out=" << (ex.timed_out ? 1 : 0) << "\n";
        if (sat) {
            row_assign.resize(in.rows.size());
            for (int r = 0; r < (int)in.rows.size(); ++r) {
                row_assign[r] = in.row_choices[r][ex.assign[r]];
            }
            print_model(in, row_assign);
            return 10;
        }
        if (!ex.timed_out) {
            write_asserted_unsat_proof(argv[2]);
            cout << "s UNSATISFIABLE\n";
            return 20;
        }
    }

    vector<vector<int>> cliques;
    if (in.nvars <= 70000 && greedy_clique_cover(in, cliques)) {
        write_unsat_cover_proof(in, cliques, argv[2]);
        cout << "c clique-cover counting contradiction cliques=" << cliques.size() << "\n";
        cout << "s UNSATISFIABLE\n";
        return 20;
    }

    double search_budget = in.nvars > 70000 ? 1.0 : max(1.0, 180.0 - elapsed_sec(start));
    if (local_search(in, row_assign, search_budget)) {
        print_model(in, row_assign);
        return 10;
    }
    cout << "c no certified result found\ns UNKNOWN\n";
    return 0;
}
