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

using namespace std;

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

static inline int lit_index(int lit) {
    int v = abs(lit) - 1;
    return (v << 1) ^ (lit < 0);
}

static inline int neg_lit_index(int idx) { return idx ^ 1; }
static inline int lit_from_index(int idx) {
    int v = (idx >> 1) + 1;
    return (idx & 1) ? -v : v;
}

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

static bool parse_dimacs(const string& path, Formula& f) {
    ifstream in(path);
    if (!in) return false;
    string tok;
    vector<int> cur;
    while (in >> tok) {
        if (tok == "c") {
            string rest;
            getline(in, rest);
        } else if (tok == "p") {
            string fmt;
            int m;
            in >> fmt >> f.nvars >> m;
            f.clauses.reserve(m);
        } else {
            int lit = stoi(tok);
            if (lit == 0) {
                f.clauses.push_back(cur);
                cur.clear();
            } else {
                cur.push_back(lit);
            }
        }
    }
    if (!cur.empty()) f.clauses.push_back(cur);
    return f.nvars > 0;
}

static bool verify_model(const Formula& f, const vector<int8_t>& val) {
    for (const auto& c : f.clauses) {
        bool sat = false;
        for (int lit : c) {
            int8_t v = val[abs(lit)];
            if ((lit > 0 && v > 0) || (lit < 0 && v < 0)) {
                sat = true;
                break;
            }
        }
        if (!sat) return false;
    }
    return true;
}

static void print_model(const vector<int8_t>& val) {
    cout << "s SATISFIABLE\n";
    int n = (int)val.size() - 1;
    int col = 0;
    cout << "v";
    for (int v = 1; v <= n; ++v) {
        int lit = val[v] >= 0 ? v : -v;
        cout << ' ' << lit;
        if (++col == 20) {
            cout << " 0\n";
            if (v != n) cout << "v";
            col = 0;
        }
    }
    if (col != 0) cout << " 0\n";
}

struct OneHotSolver {
    const Formula& f;
    string proof_path;
    int width = 0;
    vector<vector<int>> groups;          // selected original literals
    vector<int> group_clause_id;         // original 1-based clause IDs
    vector<int> group_of_var, idx_of_var;

    struct Edge {
        int a = -1, b = -1;
        vector<int> amap;        // value in a -> value in b
        vector<int> bmap;        // value in b -> value in a
        vector<vector<int>> clause_id; // [value a][value b], 1-based original clause id
    };

    vector<Edge> edges;
    vector<vector<pair<int,int>>> adj; // group -> (neighbor, edge id)
    vector<unordered_map<int,int>> edge_lookup;

    explicit OneHotSolver(const Formula& ff, string pp) : f(ff), proof_path(std::move(pp)) {}

    bool detect() {
        vector<int> orient(f.nvars + 1, 0);
        for (const auto& c : f.clauses) {
            int want = (c.size() > 2) ? 1 : -1;
            for (int lit : c) {
                int v = abs(lit);
                int s = lit > 0 ? 1 : -1;
                int o = want * s;
                if (orient[v] == 0) orient[v] = o;
                else if (orient[v] != o) return false;
            }
        }

        group_of_var.assign(f.nvars + 1, -1);
        idx_of_var.assign(f.nvars + 1, -1);
        for (int ci = 0; ci < (int)f.clauses.size(); ++ci) {
            const auto& c = f.clauses[ci];
            if (c.size() <= 2) continue;
            if (width == 0) width = (int)c.size();
            if ((int)c.size() != width || width < 2 || width > 30) return false;
            int g = (int)groups.size();
            groups.push_back(c);
            group_clause_id.push_back(ci + 1);
            for (int i = 0; i < (int)c.size(); ++i) {
                int v = abs(c[i]);
                if (group_of_var[v] != -1) return false;
                group_of_var[v] = g;
                idx_of_var[v] = i;
            }
        }
        if (groups.empty()) return false;

        map<pair<int,int>, Edge> by_pair;
        for (int ci = 0; ci < (int)f.clauses.size(); ++ci) {
            const auto& c = f.clauses[ci];
            if (c.size() != 2) continue;
            int v0 = abs(c[0]), v1 = abs(c[1]);
            int g0 = group_of_var[v0], g1 = group_of_var[v1];
            if (g0 < 0 || g1 < 0 || g0 == g1) return false;
            int i0 = idx_of_var[v0], i1 = idx_of_var[v1];
            if (groups[g0][i0] != -c[0] || groups[g1][i1] != -c[1]) return false;
            if (g0 > g1) {
                swap(g0, g1);
                swap(i0, i1);
            }
            auto key = make_pair(g0, g1);
            auto& e = by_pair[key];
            if (e.a < 0) {
                e.a = g0;
                e.b = g1;
                e.amap.assign(width, -1);
                e.bmap.assign(width, -1);
                e.clause_id.assign(width, vector<int>(width, -1));
            }
            if (e.clause_id[i0][i1] != -1) return false;
            e.clause_id[i0][i1] = ci + 1;
        }

        edges.clear();
        for (auto& kv : by_pair) {
            Edge e = std::move(kv.second);
            int cnt = 0;
            bool ok = true;
            for (int i = 0; i < width; ++i) {
                for (int j = 0; j < width; ++j) {
                    if (e.clause_id[i][j] >= 0) {
                        ++cnt;
                        if (e.amap[i] != -1 || e.bmap[j] != -1) ok = false;
                        e.amap[i] = j;
                        e.bmap[j] = i;
                    }
                }
            }
            if (!ok || cnt != width) return false;
            edges.push_back(std::move(e));
        }

        int ng = groups.size();
        adj.assign(ng, {});
        edge_lookup.assign(ng, {});
        for (int ei = 0; ei < (int)edges.size(); ++ei) {
            const auto& e = edges[ei];
            adj[e.a].push_back({e.b, ei});
            adj[e.b].push_back({e.a, ei});
            edge_lookup[e.a][e.b] = ei;
            edge_lookup[e.b][e.a] = ei;
        }
        return true;
    }

    bool edge_forbids(int g, int vg, int h, int vh) const {
        auto it = edge_lookup[g].find(h);
        if (it == edge_lookup[g].end()) return false;
        const Edge& e = edges[it->second];
        if (e.a == g) return e.amap[vg] == vh;
        return e.bmap[vg] == vh;
    }

    int forbidden_value(int g, int vg, int h) const {
        auto it = edge_lookup[g].find(h);
        if (it == edge_lookup[g].end()) return -1;
        const Edge& e = edges[it->second];
        if (e.a == g) return e.amap[vg];
        return e.bmap[vg];
    }

    vector<int> max_clique() const {
        int n = groups.size();
        vector<uint64_t> dummy;
        vector<set<int>> G(n);
        for (const auto& e : edges) {
            G[e.a].insert(e.b);
            G[e.b].insert(e.a);
        }
        vector<int> best;
        auto color_sort = [&](const vector<int>& P) {
            vector<int> order, bounds;
            vector<int> U = P;
            int color = 0;
            while (!U.empty()) {
                ++color;
                vector<int> avail = U;
                while (!avail.empty()) {
                    int bestv = avail[0];
                    for (int v : avail)
                        if (G[v].size() > G[bestv].size()) bestv = v;
                    order.push_back(bestv);
                    bounds.push_back(color);
                    U.erase(remove(U.begin(), U.end(), bestv), U.end());
                    vector<int> next;
                    for (int v : avail)
                        if (v != bestv && !G[bestv].count(v)) next.push_back(v);
                    avail.swap(next);
                }
            }
            return pair<vector<int>, vector<int>>(order, bounds);
        };
        function<void(vector<int>&, vector<int>)> expand = [&](vector<int>& R, vector<int> P) {
            if (P.empty()) {
                if (R.size() > best.size()) best = R;
                return;
            }
            auto [order, bounds] = color_sort(P);
            set<int> Pset(P.begin(), P.end());
            for (int idx = (int)order.size() - 1; idx >= 0; --idx) {
                int v = order[idx];
                if ((int)R.size() + bounds[idx] <= (int)best.size()) return;
                if (!Pset.count(v)) continue;
                vector<int> NP;
                for (int u : P) if (u != v && G[v].count(u)) NP.push_back(u);
                R.push_back(v);
                expand(R, NP);
                R.pop_back();
                Pset.erase(v);
                P.erase(remove(P.begin(), P.end(), v), P.end());
            }
        };
        vector<int> R, P(n);
        iota(P.begin(), P.end(), 0);
        expand(R, P);
        return best;
    }

    bool clique_color_maps(const vector<int>& clique, vector<vector<int>>& value_for_color) const {
        if ((int)clique.size() < width + 1) return false;
        vector<int> C(clique.begin(), clique.begin() + width + 1);
        int ref = C[0];
        vector<vector<int>> color_of(groups.size(), vector<int>(width, -1));
        for (int i = 0; i < width; ++i) color_of[ref][i] = i;
        for (int pos = 1; pos < (int)C.size(); ++pos) {
            int g = C[pos];
            auto it = edge_lookup[ref].find(g);
            if (it == edge_lookup[ref].end()) return false;
            const Edge& e = edges[it->second];
            if (e.a == ref) {
                for (int rv = 0; rv < width; ++rv) color_of[g][e.amap[rv]] = rv;
            } else {
                for (int gv = 0; gv < width; ++gv) color_of[g][gv] = e.amap[gv];
            }
        }
        for (int x = 0; x < (int)C.size(); ++x) {
            for (int y = x + 1; y < (int)C.size(); ++y) {
                int g = C[x], h = C[y];
                auto it = edge_lookup[g].find(h);
                if (it == edge_lookup[g].end()) return false;
                for (int vg = 0; vg < width; ++vg) {
                    int vh = forbidden_value(g, vg, h);
                    if (vh < 0 || color_of[g][vg] != color_of[h][vh]) return false;
                }
            }
        }
        value_for_color.assign(C.size(), vector<int>(width, -1));
        for (int p = 0; p < (int)C.size(); ++p) {
            int g = C[p];
            for (int v = 0; v < width; ++v) value_for_color[p][color_of[g][v]] = v;
            for (int c = 0; c < width; ++c)
                if (value_for_color[p][c] < 0) return false;
        }
        return true;
    }

    bool global_color_maps(vector<vector<int>>& color_of_value) const {
        int ng = groups.size();
        color_of_value.assign(ng, vector<int>(width, -1));
        queue<int> q;
        for (int s = 0; s < ng; ++s) {
            if (color_of_value[s][0] != -1) continue;
            for (int v = 0; v < width; ++v) color_of_value[s][v] = v;
            q.push(s);
            while (!q.empty()) {
                int g = q.front();
                q.pop();
                for (auto [h, ei] : adj[g]) {
                    const Edge& e = edges[ei];
                    vector<int> hm(width, -1);
                    if (e.a == g) {
                        for (int gv = 0; gv < width; ++gv)
                            hm[e.amap[gv]] = color_of_value[g][gv];
                    } else {
                        for (int gv = 0; gv < width; ++gv)
                            hm[e.bmap[gv]] = color_of_value[g][gv];
                    }
                    bool fresh = color_of_value[h][0] == -1;
                    if (fresh) {
                        color_of_value[h] = hm;
                        q.push(h);
                    } else if (color_of_value[h] != hm) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    bool dsatur_dfs(vector<int>& assign, vector<uint32_t>& dom, const vector<vector<int>>& graph) const {
        int n = assign.size();
        int best = -1, best_sat = -1, best_deg = -1, best_dom = 1000;
        for (int v = 0; v < n; ++v) {
            if (assign[v] != -1) continue;
            int pc = __builtin_popcount(dom[v]);
            if (pc == 0) return false;
            uint32_t used = 0;
            for (int u : graph[v]) if (assign[u] != -1) used |= 1u << assign[u];
            int sat = __builtin_popcount(used);
            int deg = graph[v].size();
            if (sat > best_sat || (sat == best_sat && pc < best_dom) ||
                (sat == best_sat && pc == best_dom && deg > best_deg)) {
                best = v;
                best_sat = sat;
                best_deg = deg;
                best_dom = pc;
            }
        }
        if (best == -1) return true;

        vector<pair<int,int>> colors;
        for (int c = 0; c < width; ++c) if (dom[best] & (1u << c)) {
            int impact = 0;
            for (int u : graph[best])
                if (assign[u] == -1 && (dom[u] & (1u << c))) ++impact;
            colors.push_back({impact, c});
        }
        sort(colors.begin(), colors.end());
        for (auto [impact, c] : colors) {
            vector<pair<int,uint32_t>> changed;
            assign[best] = c;
            bool ok = true;
            for (int u : graph[best]) {
                if (assign[u] == c) {
                    ok = false;
                    break;
                }
                if (assign[u] == -1 && (dom[u] & (1u << c))) {
                    changed.push_back({u, dom[u]});
                    dom[u] &= ~(1u << c);
                    if (dom[u] == 0) {
                        ok = false;
                        break;
                    }
                }
            }
            if (ok && dsatur_dfs(assign, dom, graph)) return true;
            for (auto [u, old] : changed) dom[u] = old;
            assign[best] = -1;
        }
        return false;
    }

    bool solve_sat_graph_coloring(vector<int8_t>& model) const {
        vector<vector<int>> color_of_value;
        if (!global_color_maps(color_of_value)) return false;
        int ng = groups.size();
        vector<vector<int>> graph(ng);
        for (const auto& e : edges) {
            graph[e.a].push_back(e.b);
            graph[e.b].push_back(e.a);
        }

        vector<int> assign(ng, -1);
        if (local_search_color(assign, graph)) {
            return build_model_from_colors(assign, color_of_value, model);
        }
        if (ng > 300) return false;

        assign.assign(ng, -1);
        vector<uint32_t> dom(ng, (1u << width) - 1u);
        if (!dsatur_dfs(assign, dom, graph)) return false;
        return build_model_from_colors(assign, color_of_value, model);
    }

    bool local_search_color(vector<int>& color, const vector<vector<int>>& graph) const {
        int n = graph.size();
        color.assign(n, 0);
        vector<int> order(n);
        iota(order.begin(), order.end(), 0);
        sort(order.begin(), order.end(), [&](int a, int b) {
            return graph[a].size() > graph[b].size();
        });

        uint64_t seed = 0x9e3779b97f4a7c15ULL ^ (uint64_t)n ^ ((uint64_t)width << 32);
        auto rng = [&]() {
            seed ^= seed << 7;
            seed ^= seed >> 9;
            return seed;
        };

        vector<array<int, 32>> cnt(n);
        auto init_counts = [&]() {
            for (int v = 0; v < n; ++v)
                for (int c = 0; c < 32; ++c) cnt[v][c] = 0;
            for (int v = 0; v < n; ++v)
                for (int u : graph[v]) cnt[v][color[u]]++;
        };

        int restarts = n > 1000 ? 4 : 8;
        int flips_per_restart = max(10000, min(160000, n * width * 80));
        for (int r = 0; r < restarts; ++r) {
            fill(color.begin(), color.end(), -1);
            for (int v : order) {
                int bestc = 0, best = 1e9;
                int offset = rng() % width;
                for (int k = 0; k < width; ++k) {
                    int c = (k + offset) % width;
                    int bad = 0;
                    for (int u : graph[v]) if (color[u] == c) ++bad;
                    if (bad < best) {
                        best = bad;
                        bestc = c;
                    }
                }
                color[v] = bestc;
            }
            init_counts();

            int total_conf = 0;
            for (int v = 0; v < n; ++v) total_conf += cnt[v][color[v]];
            total_conf /= 2;
            if (total_conf == 0) return true;

            int cursor = rng() % max(1, n);
            for (int step = 0; step < flips_per_restart; ++step) {
                int v = -1;
                for (int t = 0; t < n; ++t) {
                    int cand = (cursor + t) % n;
                    if (cnt[cand][color[cand]] > 0) {
                        v = cand;
                        cursor = (cand + 1) % n;
                        break;
                    }
                }
                if (v < 0) return true;
                int old = color[v];
                int bestc = old, best = cnt[v][old];
                int offset = rng() % width;
                for (int k = 0; k < width; ++k) {
                    int c = (k + offset) % width;
                    if (cnt[v][c] < best) {
                        best = cnt[v][c];
                        bestc = c;
                    }
                }
                if (bestc == old && (rng() & 31) == 0) bestc = rng() % width;
                if (bestc == old) continue;
                total_conf += cnt[v][bestc] - cnt[v][old];
                color[v] = bestc;
                for (int u : graph[v]) {
                    cnt[u][old]--;
                    cnt[u][bestc]++;
                }
                if (total_conf == 0) return true;
            }
        }
        return false;
    }

    bool build_model_from_colors(const vector<int>& assign, const vector<vector<int>>& color_of_value,
                                 vector<int8_t>& model) const {
        int ng = groups.size();
        model.assign(f.nvars + 1, -1);
        for (int g = 0; g < ng; ++g) {
            int chosen_val = -1;
            for (int v = 0; v < width; ++v) {
                if (color_of_value[g][v] == assign[g]) {
                    chosen_val = v;
                    break;
                }
            }
            if (chosen_val < 0) return false;
            for (int i = 0; i < width; ++i) {
                int lit = groups[g][i];
                int var = abs(lit);
                bool make_true = (i == chosen_val);
                model[var] = (lit > 0) == make_true ? 1 : -1;
            }
        }
        for (int v = 1; v <= f.nvars; ++v)
            if (model[v] == 0) model[v] = -1;
        return verify_model(f, model);
    }

    bool write_clique_proof(const vector<int>& clique_full) const {
        vector<vector<int>> val_for_color;
        if (!clique_color_maps(clique_full, val_for_color)) return false;
        vector<int> clique(clique_full.begin(), clique_full.begin() + width + 1);

        ofstream out(proof_path);
        if (!out) return false;
        out << "pseudo-Boolean proof version 3.0\n";
        int next_id = (int)f.clauses.size() + 1;
        map<pair<int,int>, int> pair_id;
        auto norm_pair = [](int a, int b) {
            if (a > b) swap(a, b);
            return make_pair(a, b);
        };
        auto get_pair_id = [&](int a, int b) {
            auto key = norm_pair(a, b);
            auto it = pair_id.find(key);
            if (it != pair_id.end()) return it->second;
            out << "rup -1 " << pb_lit(a) << " -1 " << pb_lit(b) << " >= -1 ;\n";
            int id = next_id++;
            pair_id[key] = id;
            return id;
        };

        vector<int> amo_ids;
        for (int color = 0; color < width; ++color) {
            vector<int> lits;
            for (int p = 0; p < (int)clique.size(); ++p) {
                int g = clique[p];
                int val = val_for_color[p][color];
                lits.push_back(groups[g][val]);
            }
            int cur = get_pair_id(lits[0], lits[1]);
            int sz = 2;
            for (int pos = 2; pos < (int)lits.size(); ++pos) {
                int y = lits[pos];
                vector<int> pids;
                pids.reserve(sz);
                for (int i = 0; i < sz; ++i) pids.push_back(get_pair_id(lits[i], y));
                out << "pol " << cur << ' ' << (sz - 1) << " *";
                for (int pid : pids) out << ' ' << pid << " +";
                out << ' ' << sz << " d ;\n";
                cur = next_id++;
                ++sz;
            }
            amo_ids.push_back(cur);
        }

        out << "pol " << group_clause_id[clique[0]];
        for (int i = 1; i < (int)clique.size(); ++i)
            out << ' ' << group_clause_id[clique[i]] << " +";
        out << " ;\n";
        int group_sum_id = next_id++;

        out << "pol " << group_sum_id;
        for (int id : amo_ids) out << ' ' << id << " +";
        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;
    }

    bool propagate_domains(vector<uint32_t>& dom, queue<int>& q) const {
        while (!q.empty()) {
            int g = q.front();
            q.pop();
            if (dom[g] == 0) return false;
            if ((dom[g] & (dom[g] - 1)) != 0) continue;
            int val = __builtin_ctz(dom[g]);
            for (auto [h, ei] : adj[g]) {
                int forbid = forbidden_value(g, val, h);
                if (forbid < 0) continue;
                uint32_t old = dom[h];
                uint32_t nd = old & ~(1u << forbid);
                if (nd != old) {
                    if (nd == 0) return false;
                    dom[h] = nd;
                    q.push(h);
                }
            }
        }
        return true;
    }

    bool dfs_color(vector<uint32_t>& dom) const {
        int best = -1, bestpc = 1000, bestdeg = -1;
        for (int g = 0; g < (int)dom.size(); ++g) {
            int pc = __builtin_popcount(dom[g]);
            if (pc == 0) return false;
            if (pc > 1) {
                int deg = adj[g].size();
                if (pc < bestpc || (pc == bestpc && deg > bestdeg)) {
                    best = g;
                    bestpc = pc;
                    bestdeg = deg;
                }
            }
        }
        if (best < 0) return true;

        vector<pair<int,int>> vals;
        for (int v = 0; v < width; ++v) if (dom[best] & (1u << v)) {
            int impact = 0;
            for (auto [h, ei] : adj[best]) {
                int fv = forbidden_value(best, v, h);
                if (fv >= 0 && (dom[h] & (1u << fv))) ++impact;
            }
            vals.push_back({impact, v});
        }
        sort(vals.begin(), vals.end());
        for (auto [impact, v] : vals) {
            auto ndom = dom;
            ndom[best] = 1u << v;
            queue<int> q;
            q.push(best);
            if (propagate_domains(ndom, q) && dfs_color(ndom)) {
                dom.swap(ndom);
                return true;
            }
        }
        return false;
    }

    bool solve_sat(vector<int8_t>& model) const {
        if (solve_sat_graph_coloring(model)) return true;

        if (groups.size() > 300) return false;

        vector<uint32_t> dom(groups.size(), (width == 32) ? 0xffffffffu : ((1u << width) - 1u));
        queue<int> q;
        if (!propagate_domains(dom, q)) return false;
        if (!dfs_color(dom)) return false;

        model.assign(f.nvars + 1, -1);
        for (int g = 0; g < (int)groups.size(); ++g) {
            if (__builtin_popcount(dom[g]) != 1) return false;
            int chosen = __builtin_ctz(dom[g]);
            for (int i = 0; i < width; ++i) {
                int lit = groups[g][i];
                int v = abs(lit);
                bool make_true = (i == chosen);
                model[v] = (lit > 0) == make_true ? 1 : -1;
            }
        }
        for (int v = 1; v <= f.nvars; ++v)
            if (model[v] == 0) model[v] = -1;
        return verify_model(f, model);
    }
};

struct CDCL {
    struct Clause {
        vector<int> lits;
        bool learnt = false;
        bool deleted = false;
        double activity = 0.0;
    };

    const Formula& f;
    string proof_path;
    vector<Clause> clauses;
    vector<vector<int>> watches;
    vector<int8_t> assigns;
    vector<int> level, reason, trail, trail_lim;
    vector<char> seen;
    vector<double> activity;
    vector<int8_t> phase;
    int qhead = 0;
    double var_inc = 1.0, var_decay = 0.95;
    ofstream proof;
    int conflicts = 0;
    int64_t loop_ticks = 0;
    chrono::steady_clock::time_point start;
    double time_limit = 540.0;

    explicit CDCL(const Formula& ff, string pp) : f(ff), proof_path(std::move(pp)) {}

    int value_lit(int lit) const {
        int8_t v = assigns[abs(lit)];
        if (v == 0) return 0;
        return (lit > 0) ? v : -v;
    }

    bool enqueue(int lit, int from) {
        int v = abs(lit);
        int val = lit > 0 ? 1 : -1;
        if (assigns[v] != 0) return assigns[v] == val;
        assigns[v] = val;
        phase[v] = val;
        level[v] = trail_lim.size();
        reason[v] = from;
        trail.push_back(lit);
        return true;
    }

    void attach_clause(int ci) {
        auto& c = clauses[ci].lits;
        if (c.size() >= 2) {
            watches[lit_index(c[0])].push_back(ci);
            watches[lit_index(c[1])].push_back(ci);
        }
    }

    bool init() {
        clauses.reserve(f.clauses.size() + 10000);
        watches.assign(2 * f.nvars, {});
        assigns.assign(f.nvars + 1, 0);
        level.assign(f.nvars + 1, 0);
        reason.assign(f.nvars + 1, -1);
        seen.assign(f.nvars + 1, 0);
        activity.assign(f.nvars + 1, 0.0);
        phase.assign(f.nvars + 1, 1);
        vector<int> occ(f.nvars + 1, 0);

        for (const auto& fc : f.clauses) {
            Clause c;
            c.lits = fc;
            sort(c.lits.begin(), c.lits.end(), [](int a, int b) {
                return abs(a) == abs(b) ? a < b : abs(a) < abs(b);
            });
            vector<int> nl;
            bool taut = false;
            for (int lit : c.lits) {
                if (!nl.empty() && nl.back() == lit) continue;
                if (!nl.empty() && abs(nl.back()) == abs(lit) && nl.back() == -lit) {
                    taut = true;
                    break;
                }
                nl.push_back(lit);
            }
            if (taut) continue;
            c.lits.swap(nl);
            int id = clauses.size();
            clauses.push_back(std::move(c));
            for (int lit : clauses.back().lits) occ[abs(lit)]++;
            if (clauses.back().lits.empty()) return false;
            if (clauses.back().lits.size() == 1) {
                if (!enqueue(clauses.back().lits[0], id)) return false;
            } else {
                attach_clause(id);
            }
        }
        for (int v = 1; v <= f.nvars; ++v) activity[v] = occ[v];
        proof.open(proof_path);
        if (proof) proof << "pseudo-Boolean proof version 3.0\n";
        start = chrono::steady_clock::now();
        return true;
    }

    int propagate() {
        while (qhead < (int)trail.size()) {
            int p = trail[qhead++];
            int false_idx = lit_index(-p);
            auto& ws = watches[false_idx];
            int out = 0;
            for (int k = 0; k < (int)ws.size(); ++k) {
                int ci = ws[k];
                Clause& cl = clauses[ci];
                if (cl.deleted) continue;
                auto& c = cl.lits;
                if (c.size() < 2) continue;
                if (c[0] != -p) swap(c[0], c[1]);
                if (c[0] != -p) {
                    ws[out++] = ci;
                    continue;
                }
                int other = c[1];
                if (value_lit(other) > 0) {
                    ws[out++] = ci;
                    continue;
                }
                bool found = false;
                for (int i = 2; i < (int)c.size(); ++i) {
                    if (value_lit(c[i]) >= 0) {
                        swap(c[0], c[i]);
                        watches[lit_index(c[0])].push_back(ci);
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    ws[out++] = ci;
                    if (value_lit(other) < 0) {
                        for (++k; k < (int)ws.size(); ++k) ws[out++] = ws[k];
                        ws.resize(out);
                        return ci;
                    }
                    if (!enqueue(other, ci)) {
                        for (++k; k < (int)ws.size(); ++k) ws[out++] = ws[k];
                        ws.resize(out);
                        return ci;
                    }
                }
            }
            ws.resize(out);
        }
        return -1;
    }

    void new_decision_level() { trail_lim.push_back(trail.size()); }

    void backtrack(int lvl) {
        if ((int)trail_lim.size() <= lvl) return;
        int target = trail_lim[lvl];
        for (int i = (int)trail.size() - 1; i >= target; --i) {
            int v = abs(trail[i]);
            assigns[v] = 0;
            reason[v] = -1;
            level[v] = 0;
        }
        trail.resize(target);
        trail_lim.resize(lvl);
        qhead = trail.size();
    }

    void bump_var(int v) {
        activity[v] += var_inc;
        if (activity[v] > 1e100) {
            for (int i = 1; i <= f.nvars; ++i) activity[i] *= 1e-100;
            var_inc *= 1e-100;
        }
    }

    void decay_activity() { var_inc /= var_decay; }

    void log_rup_clause(const vector<int>& c) {
        if (!proof) return;
        proof << "rup";
        for (int lit : c) proof << " 1 " << pb_lit(lit);
        proof << " >= 1 ;\n";
    }

    void log_unsat_footer() {
        if (!proof) return;
        proof << "rup >= 1 ;\n";
        int id = (int)f.clauses.size() + 1; // accepted with conclusion -1 too, but use latest is hard after simplification
        (void)id;
        proof << "output NONE ;\n";
        proof << "conclusion UNSAT : -1 ;\n";
        proof << "end pseudo-Boolean proof ;\n";
    }

    void analyze(int confl, vector<int>& learnt, int& backtrack_level) {
        learnt.clear();
        learnt.push_back(0);
        int pathC = 0;
        int p = 0;
        int idx = (int)trail.size() - 1;
        int ci = confl;
        do {
            Clause& cl = clauses[ci];
            cl.activity += 1.0;
            for (int lit : cl.lits) {
                int v = abs(lit);
                if (v == abs(p)) continue;
                if (!seen[v] && level[v] > 0) {
                    seen[v] = 1;
                    bump_var(v);
                    if (level[v] == (int)trail_lim.size()) ++pathC;
                    else learnt.push_back(lit);
                }
            }
            while (idx >= 0 && !seen[abs(trail[idx])]) --idx;
            p = trail[idx--];
            ci = reason[abs(p)];
            seen[abs(p)] = 0;
            --pathC;
        } while (pathC > 0);
        learnt[0] = -p;
        for (int lit : learnt) seen[abs(lit)] = 0;

        if (learnt.size() == 1) backtrack_level = 0;
        else {
            int max_i = 1;
            for (int i = 2; i < (int)learnt.size(); ++i)
                if (level[abs(learnt[i])] > level[abs(learnt[max_i])]) max_i = i;
            swap(learnt[1], learnt[max_i]);
            backtrack_level = level[abs(learnt[1])];
        }
    }

    int add_learned(const vector<int>& lits) {
        Clause c;
        c.lits = lits;
        c.learnt = true;
        c.activity = 1.0;
        int id = clauses.size();
        clauses.push_back(std::move(c));
        if (clauses[id].lits.size() >= 2) attach_clause(id);
        return id;
    }

    int pick_branch_lit() {
        int best = 0;
        double ba = -1.0;
        for (int v = 1; v <= f.nvars; ++v) {
            if (assigns[v] == 0 && activity[v] > ba) {
                ba = activity[v];
                best = v;
            }
        }
        if (best == 0) return 0;
        return phase[best] >= 0 ? best : -best;
    }

    bool timed_out() const {
        auto now = chrono::steady_clock::now();
        double sec = chrono::duration<double>(now - start).count();
        return sec > time_limit;
    }

    int solve(vector<int8_t>& model) {
        if (!init()) {
            log_unsat_footer();
            return 20;
        }
        int confl = propagate();
        if (confl != -1) {
            log_unsat_footer();
            return 20;
        }

        int restart_limit = 100;
        while (true) {
            if (((++loop_ticks) & 1023) == 0 && timed_out()) return 0;
            confl = propagate();
            if (confl != -1) {
                ++conflicts;
                if (trail_lim.empty()) {
                    log_unsat_footer();
                    return 20;
                }
                vector<int> learnt;
                int bt = 0;
                analyze(confl, learnt, bt);
                decay_activity();
                log_rup_clause(learnt);
                backtrack(bt);
                int cid = add_learned(learnt);
                enqueue(learnt[0], cid);
                if (conflicts >= restart_limit) {
                    backtrack(0);
                    restart_limit = (int)(restart_limit * 1.5) + 100;
                }
                if ((conflicts & 4095) == 0 && timed_out()) return 0;
            } else {
                int next = pick_branch_lit();
                if (next == 0) {
                    model = assigns;
                    for (int v = 1; v <= f.nvars; ++v)
                        if (model[v] == 0) model[v] = 1;
                    if (verify_model(f, model)) return 10;
                    return 0;
                }
                new_decision_level();
                enqueue(next, -1);
            }
        }
    }
};

struct ResidualDPLL {
    const Formula& f;
    vector<int8_t>& model; // 0 means unassigned
    vector<vector<int>> clauses;
    vector<int> vars;
    vector<int> trail;
    int steps = 0;
    int max_steps = 1000000;

    ResidualDPLL(const Formula& ff, vector<int8_t>& mm) : f(ff), model(mm) {}

    int lit_value(int lit) const {
        int8_t v = model[abs(lit)];
        if (v == 0) return 0;
        return lit > 0 ? v : -v;
    }

    bool assign_lit(int lit) {
        int v = abs(lit);
        int8_t val = lit > 0 ? 1 : -1;
        if (model[v] != 0) return model[v] == val;
        model[v] = val;
        trail.push_back(v);
        return true;
    }

    void undo(int checkpoint) {
        while ((int)trail.size() > checkpoint) {
            model[trail.back()] = 0;
            trail.pop_back();
        }
    }

    bool build() {
        vector<char> used(f.nvars + 1, 0);
        for (const auto& c : f.clauses) {
            bool sat = false;
            vector<int> rem;
            for (int lit : c) {
                int val = lit_value(lit);
                if (val > 0) {
                    sat = true;
                    break;
                }
                if (val == 0) rem.push_back(lit);
            }
            if (sat) continue;
            if (rem.empty()) return false;
            for (int lit : rem) {
                int v = abs(lit);
                if (!used[v]) {
                    used[v] = 1;
                    vars.push_back(v);
                }
            }
            clauses.push_back(std::move(rem));
        }
        return vars.size() <= 256 && clauses.size() <= 10000;
    }

    bool propagate() {
        bool changed = true;
        while (changed) {
            changed = false;
            for (const auto& c : clauses) {
                bool sat = false;
                int unassigned = 0, last = 0;
                for (int lit : c) {
                    int val = lit_value(lit);
                    if (val > 0) {
                        sat = true;
                        break;
                    }
                    if (val == 0) {
                        ++unassigned;
                        last = lit;
                    }
                }
                if (sat) continue;
                if (unassigned == 0) return false;
                if (unassigned == 1) {
                    if (!assign_lit(last)) return false;
                    changed = true;
                }
            }
        }
        return true;
    }

    int choose_lit() const {
        vector<int> pos(f.nvars + 1, 0), neg(f.nvars + 1, 0);
        for (const auto& c : clauses) {
            bool sat = false;
            for (int lit : c) {
                int val = lit_value(lit);
                if (val > 0) {
                    sat = true;
                    break;
                }
            }
            if (sat) continue;
            for (int lit : c) {
                if (model[abs(lit)] == 0) {
                    if (lit > 0) ++pos[abs(lit)];
                    else ++neg[abs(lit)];
                }
            }
        }
        int best = 0, score = -1;
        for (int v : vars) {
            if (model[v] != 0) continue;
            int s = pos[v] + neg[v];
            if (s > score) {
                score = s;
                best = v;
            }
        }
        if (best == 0) return 0;
        return pos[best] >= neg[best] ? best : -best;
    }

    bool dfs() {
        if (++steps > max_steps) return false;
        int start = trail.size();
        if (!propagate()) {
            undo(start);
            return false;
        }
        int lit = choose_lit();
        if (lit == 0) return true;
        int branch_start = trail.size();
        if (assign_lit(lit) && dfs()) return true;
        undo(branch_start);
        if (assign_lit(-lit) && dfs()) return true;
        undo(branch_start);
        undo(start);
        return false;
    }

    bool solve() {
        if (!build()) return false;
        if (clauses.empty()) return true;
        return dfs();
    }
};

static bool solve_residual_model(const Formula& f, vector<int8_t>& model) {
    ResidualDPLL dpll(f, model);
    if (!dpll.solve()) return false;
    for (int v = 1; v <= f.nvars; ++v)
        if (model[v] == 0) model[v] = -1;
    return verify_model(f, model);
}

static void gen_combos_rec(const vector<int>& s, int b, int pos, vector<int>& cur,
                           vector<vector<int>>& out) {
    if ((int)cur.size() == b) {
        out.push_back(cur);
        return;
    }
    for (int i = pos; i <= (int)s.size() - (b - (int)cur.size()); ++i) {
        cur.push_back(s[i]);
        gen_combos_rec(s, b, i + 1, cur, out);
        cur.pop_back();
    }
}

static vector<int> find_clique_at_least(const vector<set<int>>& G, int target) {
    vector<int> best;
    auto color_sort = [&](const vector<int>& P) {
        vector<int> order, bounds;
        vector<int> U = P;
        int color = 0;
        while (!U.empty()) {
            ++color;
            vector<int> avail = U;
            while (!avail.empty()) {
                int bestv = avail[0];
                for (int v : avail)
                    if (G[v].size() > G[bestv].size()) bestv = v;
                order.push_back(bestv);
                bounds.push_back(color);
                U.erase(remove(U.begin(), U.end(), bestv), U.end());
                vector<int> next;
                for (int v : avail)
                    if (v != bestv && !G[bestv].count(v)) next.push_back(v);
                avail.swap(next);
            }
        }
        return pair<vector<int>, vector<int>>(order, bounds);
    };
    function<bool(vector<int>&, vector<int>)> expand = [&](vector<int>& R, vector<int> P) {
        if ((int)R.size() >= target) {
            best = R;
            return true;
        }
        if ((int)R.size() + (int)P.size() < target) return false;
        if (P.empty()) {
            if (R.size() > best.size()) best = R;
            return false;
        }
        auto [order, bounds] = color_sort(P);
        set<int> Pset(P.begin(), P.end());
        for (int idx = (int)order.size() - 1; idx >= 0; --idx) {
            int v = order[idx];
            if ((int)R.size() + bounds[idx] < target && (int)R.size() + bounds[idx] <= (int)best.size())
                return false;
            if (!Pset.count(v)) continue;
            vector<int> NP;
            for (int u : P) if (u != v && G[v].count(u)) NP.push_back(u);
            R.push_back(v);
            if (expand(R, NP)) return true;
            R.pop_back();
            Pset.erase(v);
            P.erase(remove(P.begin(), P.end(), v), P.end());
            if ((int)R.size() + (int)P.size() < target) return false;
        }
        return false;
    };
    vector<int> R, P(G.size());
    iota(P.begin(), P.end(), 0);
    expand(R, P);
    return best;
}

struct BitVectorCSPSolver {
    const Formula& f;
    string proof_path;
    int b = 0, L = 0, width = 0;
    vector<vector<int>> groups;
    vector<int> group_of_var, idx_of_var;
    vector<vector<int>> allowed_values;
    vector<array<int, 16>> color_of_value;
    vector<array<int, 16>> value_of_color;

    struct TableEdge {
        int a = -1, b = -1;
        vector<pair<int,int>> pairs;
    };

    struct Constraint {
        int a = -1, b = -1;
        array<uint16_t, 16> forbid{};
        array<array<uint16_t, 16>, 16> weight{};
    };

    vector<TableEdge> table_edges;
    vector<Constraint> constraints;
    vector<vector<int>> constraint_adj;

    explicit BitVectorCSPSolver(const Formula& ff, string pp) : f(ff), proof_path(std::move(pp)) {}

    static long long pair_key(int a, int z) {
        if (a > z) swap(a, z);
        return (long long)a << 32 | (unsigned)z;
    }

    using Perm = array<int, 16>;

    Perm identity_perm() const {
        Perm p;
        p.fill(-1);
        for (int i = 0; i < width; ++i) p[i] = i;
        return p;
    }

    Perm compose_perm(const Perm& first, const Perm& second) const {
        Perm out;
        out.fill(-1);
        for (int i = 0; i < width; ++i) out[i] = second[first[i]];
        return out;
    }

    Perm invert_perm(const Perm& p) const {
        Perm inv;
        inv.fill(-1);
        for (int i = 0; i < width; ++i) inv[p[i]] = i;
        return inv;
    }

    bool is_perm(const Perm& p) const {
        uint32_t seen = 0;
        for (int i = 0; i < width; ++i) {
            if (p[i] < 0 || p[i] >= width) return false;
            uint32_t bit = 1u << p[i];
            if (seen & bit) return false;
            seen |= bit;
        }
        return seen == ((1u << width) - 1u);
    }

    bool allowed_permutation(const Constraint& con, Perm& perm) const {
        uint32_t full = (1u << width) - 1u;
        perm.fill(-1);
        uint32_t cols = 0;
        for (int c = 0; c < width; ++c) {
            uint32_t allowed = full & ~con.forbid[c];
            if (__builtin_popcount(allowed) != 1) return false;
            int v = __builtin_ctz(allowed);
            if (cols & (1u << v)) return false;
            cols |= 1u << v;
            perm[c] = v;
        }
        return cols == full;
    }

    bool forbidden_permutation(const Constraint& con, Perm& perm) const {
        uint32_t cols = 0;
        perm.fill(-1);
        for (int c = 0; c < width; ++c) {
            uint32_t row = con.forbid[c] & ((1u << width) - 1u);
            if (__builtin_popcount(row) != 1) return false;
            int v = __builtin_ctz(row);
            if (cols & (1u << v)) return false;
            cols |= 1u << v;
            perm[c] = v;
        }
        return cols == ((1u << width) - 1u);
    }

    bool recover_groups() {
        L = 0;
        for (const auto& c : f.clauses)
            if (c.size() > 2) L = max(L, (int)c.size());
        if (L == 0 || (L & 1)) return false;
        b = L / 2;
        if (b < 2 || b > 4) return false;
        int max_width = 1 << b;

        map<vector<int>, int> support_count;
        for (const auto& c : f.clauses) {
            if ((int)c.size() != L) continue;
            vector<int> s;
            s.reserve(c.size());
            for (int lit : c) s.push_back(abs(lit));
            sort(s.begin(), s.end());
            s.erase(unique(s.begin(), s.end()), s.end());
            if ((int)s.size() == L) support_count[s]++;
        }
        if (support_count.empty()) return false;
        map<int, int> hist;
        for (const auto& kv : support_count) hist[kv.second]++;
        int width_freq = 0;
        for (const auto& kv : hist) {
            if (kv.second > width_freq) {
                width = kv.first;
                width_freq = kv.second;
            }
        }
        if (width < 3 || width > max_width) return false;
        if (width_freq * 2 < (int)support_count.size()) return false;

        vector<vector<int>> supports;
        supports.reserve(support_count.size());
        for (const auto& kv : support_count) supports.push_back(kv.first);

        unordered_map<long long, int> pair_count;
        pair_count.reserve(supports.size() * L);
        auto vpair_key = [](int a, int z) {
            if (a > z) swap(a, z);
            return (long long)a << 32 | (unsigned)z;
        };
        for (const auto& s : supports) {
            for (int i = 0; i < (int)s.size(); ++i)
                for (int j = i + 1; j < (int)s.size(); ++j)
                    pair_count[vpair_key(s[i], s[j])]++;
        }

        vector<vector<pair<int,int>>> neigh(f.nvars + 1);
        for (const auto& kv : pair_count) {
            if (kv.second <= width) continue;
            int a = (int)(kv.first >> 32);
            int z = (int)(kv.first & 0xffffffffu);
            if (a >= 1 && a <= f.nvars && z >= 1 && z <= f.nvars) {
                neigh[a].push_back({kv.second, z});
                neigh[z].push_back({kv.second, a});
            }
        }

        set<vector<int>> group_set;
        for (int v = 1; v <= f.nvars; ++v) {
            auto ns = neigh[v];
            if ((int)ns.size() < b - 1) continue;
            sort(ns.begin(), ns.end(), greater<pair<int,int>>());
            vector<int> g{v};
            for (int i = 0; i < b - 1; ++i) g.push_back(ns[i].second);
            sort(g.begin(), g.end());
            group_set.insert(g);
        }

        auto close_from_supports = [&]() {
            bool changed = true;
            while (changed) {
                changed = false;
                for (const auto& s : supports) {
                    vector<vector<int>> combos;
                    vector<int> cur;
                    gen_combos_rec(s, b, 0, cur, combos);
                    for (const auto& comb : combos) {
                        if (!group_set.count(comb)) continue;
                        vector<int> rest;
                        rest.reserve(b);
                        for (int v : s)
                            if (!binary_search(comb.begin(), comb.end(), v)) rest.push_back(v);
                        if ((int)rest.size() == b && !group_set.count(rest)) {
                            group_set.insert(rest);
                            changed = true;
                        }
                    }
                }
            }
        };
        close_from_supports();

        vector<vector<int>> by_var(f.nvars + 1);
        for (int i = 0; i < (int)supports.size(); ++i)
            for (int v : supports[i])
                if (v >= 1 && v <= f.nvars) by_var[v].push_back(i);
        for (const auto& ids : by_var) {
            if (ids.size() < 2 || ids.size() > 2000) continue;
            for (int ii = 0; ii < (int)ids.size(); ++ii) {
                const auto& A = supports[ids[ii]];
                for (int jj = ii + 1; jj < (int)ids.size(); ++jj) {
                    const auto& B = supports[ids[jj]];
                    vector<int> inter;
                    inter.reserve(b);
                    int p = 0, q = 0;
                    while (p < (int)A.size() && q < (int)B.size()) {
                        if (A[p] == B[q]) {
                            inter.push_back(A[p]);
                            ++p; ++q;
                        } else if (A[p] < B[q]) {
                            ++p;
                        } else {
                            ++q;
                        }
                    }
                    if ((int)inter.size() == b) group_set.insert(inter);
                }
            }
        }
        close_from_supports();

        groups.assign(group_set.begin(), group_set.end());
        if ((int)groups.size() < width) return false;
        group_of_var.assign(f.nvars + 1, -1);
        idx_of_var.assign(f.nvars + 1, -1);
        for (int gi = 0; gi < (int)groups.size(); ++gi) {
            if ((int)groups[gi].size() != b) return false;
            for (int i = 0; i < b; ++i) {
                int v = groups[gi][i];
                if (v < 1 || v > f.nvars || group_of_var[v] != -1) return false;
                group_of_var[v] = gi;
                idx_of_var[v] = i;
            }
        }
        return true;
    }

    int raw_value_for_group(const vector<int>& clause, int g) const {
        int val = 0, seen = 0;
        for (int lit : clause) {
            int v = abs(lit);
            if (v < 1 || v > f.nvars || group_of_var[v] != g) continue;
            int bit = lit > 0 ? 0 : 1; // value that makes the literal false
            val |= bit << idx_of_var[v];
            ++seen;
        }
        return seen == b ? val : -1;
    }

    bool lit_true_raw(int lit, int raw) const {
        int v = abs(lit);
        int bit = (raw >> idx_of_var[v]) & 1;
        return lit > 0 ? bit != 0 : bit == 0;
    }

    bool build_tables_and_domains() {
        vector<vector<vector<int>>> same_binary(groups.size());
        struct BinarySide { int g0, g1, lit0, lit1; };
        vector<BinarySide> binary_between;
        map<pair<int,int>, vector<vector<int>>> raw_table_clauses;

        for (const auto& c : f.clauses) {
            if (c.size() == 2) {
                int g0 = group_of_var[abs(c[0])];
                int g1 = group_of_var[abs(c[1])];
                if (g0 >= 0 && g1 >= 0) {
                    if (g0 == g1) same_binary[g0].push_back(c);
                    else binary_between.push_back({g0, g1, c[0], c[1]});
                }
            } else if ((int)c.size() == L) {
                int gfirst = -1, gsecond = -1;
                bool bad = false;
                for (int lit : c) {
                    int g = group_of_var[abs(lit)];
                    if (g < 0) {
                        bad = true;
                        break;
                    }
                    if (gfirst < 0) gfirst = g;
                    else if (g != gfirst && gsecond < 0) gsecond = g;
                    else if (g != gfirst && g != gsecond) {
                        bad = true;
                        break;
                    }
                }
                if (!bad && gfirst >= 0 && gsecond >= 0) {
                    if (gfirst > gsecond) swap(gfirst, gsecond);
                    raw_table_clauses[{gfirst, gsecond}].push_back(c);
                }
            }
        }

        allowed_values.assign(groups.size(), {});
        for (int g = 0; g < (int)groups.size(); ++g) {
            for (int raw = 0; raw < (1 << b); ++raw) {
                bool ok = true;
                for (const auto& c : same_binary[g]) {
                    bool sat = false;
                    for (int lit : c) {
                        if (lit_true_raw(lit, raw)) {
                            sat = true;
                            break;
                        }
                    }
                    if (!sat) {
                        ok = false;
                        break;
                    }
                }
                if (ok) allowed_values[g].push_back(raw);
            }
            if ((int)allowed_values[g].size() != width) return false;
        }

        table_edges.clear();
        vector<vector<pair<int,int>>> table_adj(groups.size());
        for (auto& kv : raw_table_clauses) {
            int a = kv.first.first, z = kv.first.second;
            if ((int)kv.second.size() != width) return false;
            TableEdge e;
            e.a = a;
            e.b = z;
            vector<int> seen_a(1 << b, 0), seen_b(1 << b, 0);
            vector<char> allowed_a(1 << b, 0), allowed_b(1 << b, 0);
            for (int raw : allowed_values[a]) allowed_a[raw] = 1;
            for (int raw : allowed_values[z]) allowed_b[raw] = 1;
            for (const auto& c : kv.second) {
                int va = raw_value_for_group(c, a);
                int vz = raw_value_for_group(c, z);
                if (va < 0 || vz < 0 || !allowed_a[va] || !allowed_b[vz]) return false;
                if (seen_a[va]++ || seen_b[vz]++) return false;
                e.pairs.push_back({va, vz});
            }
            int id = table_edges.size();
            table_edges.push_back(std::move(e));
            table_adj[a].push_back({z, id});
            table_adj[z].push_back({a, id});
        }
        if (table_edges.empty()) return false;

        color_of_value.assign(groups.size(), {});
        value_of_color.assign(groups.size(), {});
        for (int g = 0; g < (int)groups.size(); ++g) {
            color_of_value[g].fill(-1);
            value_of_color[g].fill(-1);
        }
        queue<int> q;
        for (int s = 0; s < (int)groups.size(); ++s) {
            if (color_of_value[s][allowed_values[s][0]] != -1) continue;
            for (int c = 0; c < width; ++c) {
                int raw = allowed_values[s][c];
                color_of_value[s][raw] = c;
                value_of_color[s][c] = raw;
            }
            q.push(s);
            while (!q.empty()) {
                int g = q.front();
                q.pop();
                for (auto [h, ei] : table_adj[g]) {
                    const TableEdge& e = table_edges[ei];
                    array<int, 16> h_color;
                    h_color.fill(-1);
                    if (e.a == g) {
                        for (auto [vg, vh] : e.pairs) h_color[vh] = color_of_value[g][vg];
                    } else {
                        for (auto [vh, vg] : e.pairs) h_color[vh] = color_of_value[g][vg];
                    }
                    bool fresh = color_of_value[h][allowed_values[h][0]] == -1;
                    if (fresh) {
                        for (int raw : allowed_values[h]) {
                            if (h_color[raw] < 0) return false;
                            color_of_value[h][raw] = h_color[raw];
                            value_of_color[h][h_color[raw]] = raw;
                        }
                        q.push(h);
                    } else {
                        for (int raw : allowed_values[h])
                            if (color_of_value[h][raw] != h_color[raw]) return false;
                    }
                }
            }
        }

        constraints.clear();
        constraint_adj.assign(groups.size(), {});
        unordered_map<long long, int> cindex;
        cindex.reserve(table_edges.size() + binary_between.size());
        auto add_forbid = [&](int a, int z, int ca, int cz) {
            if (a == z) return;
            if (a > z) {
                swap(a, z);
                swap(ca, cz);
            }
            long long key = pair_key(a, z);
            auto it = cindex.find(key);
            int id;
            if (it == cindex.end()) {
                Constraint con;
                con.a = a;
                con.b = z;
                con.forbid.fill(0);
                for (auto& row : con.weight) row.fill(0);
                id = constraints.size();
                constraints.push_back(con);
                cindex[key] = id;
                constraint_adj[a].push_back(id);
                constraint_adj[z].push_back(id);
            } else {
                id = it->second;
            }
            constraints[id].forbid[ca] |= (uint16_t)(1u << cz);
            if (constraints[id].weight[ca][cz] != numeric_limits<uint16_t>::max())
                constraints[id].weight[ca][cz]++;
        };

        for (const auto& e : table_edges) {
            for (auto [va, vz] : e.pairs)
                add_forbid(e.a, e.b, color_of_value[e.a][va], color_of_value[e.b][vz]);
        }
        for (const auto& side : binary_between) {
            for (int c0 = 0; c0 < width; ++c0) {
                int raw0 = value_of_color[side.g0][c0];
                for (int c1 = 0; c1 < width; ++c1) {
                    int raw1 = value_of_color[side.g1][c1];
                    bool t0 = lit_true_raw(side.lit0, raw0);
                    bool t1 = lit_true_raw(side.lit1, raw1);
                    if (!t0 && !t1) add_forbid(side.g0, side.g1, c0, c1);
                }
            }
        }
        return !constraints.empty();
    }

    bool constraint_bad(const Constraint& con, int g, int cg, int h, int ch) const {
        if (con.a == g) return (con.forbid[cg] >> ch) & 1u;
        return (con.forbid[ch] >> cg) & 1u;
    }

    int conflict_count_color(const vector<int>& color, int g, int c) const {
        int bad = 0;
        for (int ci : constraint_adj[g]) {
            const Constraint& con = constraints[ci];
            int h = con.a ^ con.b ^ g;
            int ch = color[h];
            if (ch < 0) continue;
            if (constraint_bad(con, g, c, h, ch)) ++bad;
        }
        return bad;
    }

    int total_conflicts(const vector<int>& color) const {
        int bad = 0;
        for (const auto& con : constraints) {
            int ca = color[con.a], cb = color[con.b];
            if (ca >= 0 && cb >= 0) bad += con.weight[ca][cb];
        }
        return bad;
    }

    bool local_search(vector<int>& color, bool debug = false) const {
        int n = groups.size();
        color.assign(n, -1);
        vector<int> order(n);
        iota(order.begin(), order.end(), 0);
        sort(order.begin(), order.end(), [&](int a, int z) {
            return constraint_adj[a].size() > constraint_adj[z].size();
        });

        uint64_t seed = 0xbf58476d1ce4e5b9ULL ^ (uint64_t)n ^ ((uint64_t)width << 40);
        auto rng = [&]() {
            seed ^= seed << 7;
            seed ^= seed >> 9;
            return seed;
        };

        int restarts = n > 2000 ? 2 : 8;
        int flips_per_restart = n > 2000 ? 70000 : max(50000, min(220000, n * width * 90));
        for (int r = 0; r < restarts; ++r) {
            fill(color.begin(), color.end(), -1);
            for (int g : order) {
                int bestc = 0, best = 1000000;
                int offset = rng() % width;
                for (int k = 0; k < width; ++k) {
                    int c = (k + offset) % width;
                    int bad = conflict_count_color(color, g, c);
                    if (bad < best) {
                        best = bad;
                        bestc = c;
                    }
                }
                color[g] = bestc;
            }

            vector<array<int, 16>> badcnt(n);
            for (int g = 0; g < n; ++g)
                for (int c = 0; c < 16; ++c) badcnt[g][c] = 0;
            int total = 0;
            for (const auto& con : constraints) {
                int ca = color[con.a], cb = color[con.b];
                total += con.weight[ca][cb];
                for (int c = 0; c < width; ++c) {
                    badcnt[con.a][c] += con.weight[c][cb];
                    badcnt[con.b][c] += con.weight[ca][c];
                }
            }

            if (debug) cerr << "bv restart " << r << " conflicts=" << total << "\n";
            if (total == 0) return true;
            int best_total = total;
            vector<array<int, 16>> tabu(n);
            for (int g = 0; g < n; ++g)
                for (int c = 0; c < 16; ++c) tabu[g][c] = 0;
            vector<int> conflicted;
            for (int step = 0; step < flips_per_restart; ++step) {
                conflicted.clear();
                for (int g = 0; g < n; ++g)
                    if (badcnt[g][color[g]] > 0) conflicted.push_back(g);
                if (conflicted.empty()) return true;

                int best_delta = 1000000;
                vector<pair<int,int>> moves;
                int samples = conflicted.size() <= 700 ? (int)conflicted.size() : 260;
                for (int si = 0; si < samples; ++si) {
                    int g = conflicted.size() <= 700 ? conflicted[si] : conflicted[rng() % conflicted.size()];
                    int old = color[g];
                    int old_bad = badcnt[g][old];
                    int offset = rng() % width;
                    for (int k = 0; k < width; ++k) {
                        int c = (k + offset) % width;
                        if (c == old) continue;
                        int delta = badcnt[g][c] - old_bad;
                        bool tabu_ok = step >= tabu[g][c] || total + delta < best_total;
                        if (!tabu_ok) continue;
                        if (delta < best_delta) {
                            best_delta = delta;
                            moves.clear();
                            moves.push_back({g, c});
                        } else if (delta == best_delta && moves.size() < 128) {
                            moves.push_back({g, c});
                        }
                    }
                }
                int g, bestc;
                if (moves.empty()) {
                    g = conflicted[rng() % conflicted.size()];
                    bestc = rng() % width;
                    if (bestc == color[g]) bestc = (bestc + 1) % width;
                } else {
                    auto mv = moves[rng() % moves.size()];
                    g = mv.first;
                    bestc = mv.second;
                }
                int old = color[g];
                if (bestc == old) continue;
                int old_bad = badcnt[g][old];
                total += badcnt[g][bestc] - old_bad;
                int tenure = 7 + (int)(rng() % 11) + min(40, total / 20);
                tabu[g][old] = step + tenure;

                for (int ci : constraint_adj[g]) {
                    const Constraint& con = constraints[ci];
                    int h = con.a ^ con.b ^ g;
                    if (con.a == g) {
                        for (int ch = 0; ch < width; ++ch) {
                            int before = con.weight[old][ch];
                            int after = con.weight[bestc][ch];
                            badcnt[h][ch] += after - before;
                        }
                    } else {
                        for (int ch = 0; ch < width; ++ch) {
                            int before = con.weight[ch][old];
                            int after = con.weight[ch][bestc];
                            badcnt[h][ch] += after - before;
                        }
                    }
                }
                color[g] = bestc;
                if (total < best_total) best_total = total;
                if (total == 0) return true;
            }
            if (debug) cerr << "bv restart " << r << " end_conflicts=" << total << "\n";
        }
        return false;
    }

    bool build_model(const vector<int>& color, vector<int8_t>& model) const {
        model.assign(f.nvars + 1, 0);
        for (int g = 0; g < (int)groups.size(); ++g) {
            if (color[g] < 0 || color[g] >= width) return false;
            int raw = value_of_color[g][color[g]];
            if (raw < 0) return false;
            for (int i = 0; i < b; ++i) {
                int var = groups[g][i];
                model[var] = ((raw >> i) & 1) ? 1 : -1;
            }
        }
        return solve_residual_model(f, model);
    }

    bool solve_high_level_cdcl(vector<int>& color, bool debug) const {
        Formula hf;
        hf.nvars = (int)groups.size() * width;
        auto hvar = [&](int g, int c) { return g * width + c + 1; };
        hf.clauses.reserve(groups.size() * (1 + width * (width - 1) / 2) + constraints.size() * width);
        for (int g = 0; g < (int)groups.size(); ++g) {
            vector<int> atleast;
            atleast.reserve(width);
            for (int c = 0; c < width; ++c) atleast.push_back(hvar(g, c));
            hf.clauses.push_back(std::move(atleast));
            for (int a = 0; a < width; ++a)
                for (int z = a + 1; z < width; ++z)
                    hf.clauses.push_back({-hvar(g, a), -hvar(g, z)});
        }
        for (const auto& con : constraints) {
            for (int ca = 0; ca < width; ++ca) {
                uint16_t row = con.forbid[ca];
                for (int cb = 0; cb < width; ++cb) {
                    if ((row >> cb) & 1u)
                        hf.clauses.push_back({-hvar(con.a, ca), -hvar(con.b, cb)});
                }
            }
        }
        vector<int8_t> hmodel;
        CDCL cdcl(hf, proof_path);
        cdcl.time_limit = groups.size() > 2000 ? 90.0 : 45.0;
        int res = cdcl.solve(hmodel);
        if (debug) cerr << "bv high cdcl res=" << res << " vars=" << hf.nvars
                        << " clauses=" << hf.clauses.size() << "\n";
        if (res != 10) return false;
        color.assign(groups.size(), -1);
        for (int g = 0; g < (int)groups.size(); ++g) {
            for (int c = 0; c < width; ++c) {
                if (hmodel[hvar(g, c)] > 0) {
                    if (color[g] != -1) return false;
                    color[g] = c;
                }
            }
            if (color[g] < 0) return false;
        }
        return true;
    }

    struct PermDSU {
        const BitVectorCSPSolver& owner;
        vector<int> parent, rnk;
        vector<Perm> to_parent; // maps this node's color to parent color

        explicit PermDSU(const BitVectorCSPSolver& o, int n) : owner(o), parent(n), rnk(n, 0), to_parent(n) {
            iota(parent.begin(), parent.end(), 0);
            for (int i = 0; i < n; ++i) to_parent[i] = owner.identity_perm();
        }

        pair<int, Perm> find(int x) {
            if (parent[x] == x) return {x, owner.identity_perm()};
            int p = parent[x];
            auto pr = find(p);
            to_parent[x] = owner.compose_perm(to_parent[x], pr.second);
            parent[x] = pr.first;
            return {parent[x], to_parent[x]};
        }

        bool unite(int a, int b, const Perm& a_to_b) {
            auto fa = find(a);
            auto fb = find(b);
            int ra = fa.first, rb = fb.first;
            const Perm& a_to_ra = fa.second;
            const Perm& b_to_rb = fb.second;
            if (ra == rb) {
                for (int c = 0; c < owner.width; ++c)
                    if (a_to_ra[c] != b_to_rb[a_to_b[c]]) return false;
                return true;
            }

            Perm rb_to_ra;
            rb_to_ra.fill(-1);
            for (int c = 0; c < owner.width; ++c) {
                int rb_col = b_to_rb[a_to_b[c]];
                int ra_col = a_to_ra[c];
                if (rb_to_ra[rb_col] != -1 && rb_to_ra[rb_col] != ra_col) return false;
                rb_to_ra[rb_col] = ra_col;
            }
            if (!owner.is_perm(rb_to_ra)) return false;

            if (rnk[ra] < rnk[rb]) {
                Perm ra_to_rb = owner.invert_perm(rb_to_ra);
                parent[ra] = rb;
                to_parent[ra] = ra_to_rb;
            } else {
                parent[rb] = ra;
                to_parent[rb] = rb_to_ra;
                if (rnk[ra] == rnk[rb]) ++rnk[ra];
            }
            return true;
        }
    };

    struct CompConstraint {
        int a = -1, b = -1;
        array<uint16_t, 16> forbid{};
        array<array<uint16_t, 16>, 16> weight{};
    };

    bool propagate_comp_domains(vector<uint16_t>& dom, const vector<CompConstraint>& cons) const {
        bool changed = true;
        uint16_t full = (uint16_t)((1u << width) - 1u);
        for (uint16_t& d : dom) d &= full;
        while (changed) {
            changed = false;
            for (const auto& con : cons) {
                uint16_t da = dom[con.a], db = dom[con.b];
                uint16_t nda = da, ndb = db;
                for (int ca = 0; ca < width; ++ca) if (da & (1u << ca)) {
                    uint16_t support = db & (uint16_t)~con.forbid[ca];
                    if (support == 0) nda &= (uint16_t)~(1u << ca);
                }
                for (int cb = 0; cb < width; ++cb) if (db & (1u << cb)) {
                    bool ok = false;
                    for (int ca = 0; ca < width; ++ca) {
                        if ((da & (1u << ca)) && ((con.forbid[ca] & (1u << cb)) == 0)) {
                            ok = true;
                            break;
                        }
                    }
                    if (!ok) ndb &= (uint16_t)~(1u << cb);
                }
                if (nda == 0 || ndb == 0) return false;
                if (nda != da) {
                    dom[con.a] = nda;
                    changed = true;
                }
                if (ndb != db) {
                    dom[con.b] = ndb;
                    changed = true;
                }
            }
        }
        return true;
    }

    bool local_search_components(const vector<CompConstraint>& cons, const vector<vector<int>>& adj,
                                 const vector<uint16_t>& dom, vector<int>& color,
                                 chrono::steady_clock::time_point deadline, bool debug) const {
        int n = dom.size();
        color.assign(n, -1);
        vector<int> order(n);
        iota(order.begin(), order.end(), 0);
        sort(order.begin(), order.end(), [&](int a, int b) {
            int pa = __builtin_popcount(dom[a]), pb = __builtin_popcount(dom[b]);
            if (pa != pb) return pa < pb;
            return adj[a].size() > adj[b].size();
        });

        uint64_t seed = 0x94d049bb133111ebULL ^ (uint64_t)n ^ ((uint64_t)cons.size() << 17) ^ ((uint64_t)width << 48);
        auto rng = [&]() {
            seed ^= seed << 7;
            seed ^= seed >> 9;
            return seed;
        };

        auto pair_weight = [&](const CompConstraint& con, int g, int cg, int h, int ch) -> int {
            if (con.a == g) return con.weight[cg][ch];
            return con.weight[ch][cg];
        };

        int restarts = n > 1500 ? 10 : 20;
        int flips_per_restart = max(60000, min(500000, n * width * 180));
        for (int r = 0; r < restarts; ++r) {
            if (chrono::steady_clock::now() > deadline) return false;
            fill(color.begin(), color.end(), -1);
            for (int g : order) {
                int bestc = -1, best = numeric_limits<int>::max();
                int offset = rng() % width;
                for (int k = 0; k < width; ++k) {
                    int c = (k + offset) % width;
                    if ((dom[g] & (1u << c)) == 0) continue;
                    int bad = 0;
                    for (int ci : adj[g]) {
                        const auto& con = cons[ci];
                        int h = con.a ^ con.b ^ g;
                        if (color[h] >= 0) bad += pair_weight(con, g, c, h, color[h]);
                    }
                    if (bad < best) {
                        best = bad;
                        bestc = c;
                    }
                }
                if (bestc < 0) return false;
                color[g] = bestc;
            }

            vector<array<int, 16>> badcnt(n);
            for (int g = 0; g < n; ++g)
                for (int c = 0; c < 16; ++c) badcnt[g][c] = 0;
            int total = 0;
            for (const auto& con : cons) {
                int ca = color[con.a], cb = color[con.b];
                total += con.weight[ca][cb];
                for (int c = 0; c < width; ++c) {
                    badcnt[con.a][c] += con.weight[c][cb];
                    badcnt[con.b][c] += con.weight[ca][c];
                }
            }
            if (debug) cerr << "bv perm restart " << r << " conflicts=" << total << "\n";
            if (total == 0) return true;

            int best_total = total;
            vector<array<int, 16>> tabu(n);
            for (int g = 0; g < n; ++g)
                for (int c = 0; c < 16; ++c) tabu[g][c] = 0;
            vector<int> conflicted;
            conflicted.reserve(n);
            for (int step = 0; step < flips_per_restart; ++step) {
                if ((step & 4095) == 0 && chrono::steady_clock::now() > deadline) return false;
                conflicted.clear();
                for (int g = 0; g < n; ++g)
                    if (badcnt[g][color[g]] > 0) conflicted.push_back(g);
                if (conflicted.empty()) return true;

                int best_delta = numeric_limits<int>::max();
                vector<pair<int,int>> moves;
                int samples = conflicted.size() <= 600 ? (int)conflicted.size() : 260;
                for (int si = 0; si < samples; ++si) {
                    int g = conflicted.size() <= 600 ? conflicted[si] : conflicted[rng() % conflicted.size()];
                    int old = color[g];
                    int old_bad = badcnt[g][old];
                    int offset = rng() % width;
                    for (int k = 0; k < width; ++k) {
                        int c = (k + offset) % width;
                        if (c == old || (dom[g] & (1u << c)) == 0) continue;
                        int delta = badcnt[g][c] - old_bad;
                        bool tabu_ok = step >= tabu[g][c] || total + delta < best_total;
                        if (!tabu_ok) continue;
                        if (delta < best_delta) {
                            best_delta = delta;
                            moves.clear();
                            moves.push_back({g, c});
                        } else if (delta == best_delta && moves.size() < 128) {
                            moves.push_back({g, c});
                        }
                    }
                }

                int g, bestc;
                if (moves.empty()) {
                    g = conflicted[rng() % conflicted.size()];
                    vector<int> vals;
                    for (int c = 0; c < width; ++c)
                        if ((dom[g] & (1u << c)) && c != color[g]) vals.push_back(c);
                    if (vals.empty()) continue;
                    bestc = vals[rng() % vals.size()];
                } else {
                    auto mv = moves[rng() % moves.size()];
                    g = mv.first;
                    bestc = mv.second;
                }

                int old = color[g];
                if (bestc == old) continue;
                total += badcnt[g][bestc] - badcnt[g][old];
                int tenure = 7 + (int)(rng() % 13) + min(50, total / 20);
                tabu[g][old] = step + tenure;
                for (int ci : adj[g]) {
                    const auto& con = cons[ci];
                    int h = con.a ^ con.b ^ g;
                    if (con.a == g) {
                        for (int ch = 0; ch < width; ++ch)
                            badcnt[h][ch] += con.weight[bestc][ch] - con.weight[old][ch];
                    } else {
                        for (int ch = 0; ch < width; ++ch)
                            badcnt[h][ch] += con.weight[ch][bestc] - con.weight[ch][old];
                    }
                }
                color[g] = bestc;
                if (total < best_total) best_total = total;
                if (total == 0) return true;
            }
        }
        return false;
    }

    bool solve_permutation_coloring(vector<int8_t>& model, bool debug) {
        vector<char> is_eq(constraints.size(), 0), is_neq(constraints.size(), 0);
        vector<Perm> eq_perm(constraints.size()), neq_perm(constraints.size());
        int eq_count = 0, neq_count = 0;
        for (int i = 0; i < (int)constraints.size(); ++i) {
            if (allowed_permutation(constraints[i], eq_perm[i])) {
                is_eq[i] = 1;
                ++eq_count;
            } else if (forbidden_permutation(constraints[i], neq_perm[i])) {
                is_neq[i] = 1;
                ++neq_count;
            } else {
                return false;
            }
        }
        if (eq_count == 0) return false;

        PermDSU dsu(*this, groups.size());
        for (int i = 0; i < (int)constraints.size(); ++i) {
            if (!is_eq[i]) continue;
            if (!dsu.unite(constraints[i].a, constraints[i].b, eq_perm[i])) return false;
        }

        map<int, int> root_to_comp;
        vector<int> group_comp(groups.size(), -1);
        vector<Perm> group_to_comp(groups.size()), comp_to_group(groups.size());
        for (int g = 0; g < (int)groups.size(); ++g) {
            auto fg = dsu.find(g);
            int root = fg.first;
            auto it = root_to_comp.find(root);
            if (it == root_to_comp.end()) {
                int id = root_to_comp.size();
                it = root_to_comp.emplace(root, id).first;
            }
            group_comp[g] = it->second;
            group_to_comp[g] = fg.second;
            comp_to_group[g] = invert_perm(fg.second);
        }
        int nc = root_to_comp.size();
        vector<uint16_t> dom(nc, (uint16_t)((1u << width) - 1u));

        vector<CompConstraint> comp_cons;
        map<pair<int,int>, int> con_index;
        auto add_forbid = [&](int a, int b, int ca, int cb, int w) {
            if (a == b) return;
            if (a > b) {
                swap(a, b);
                swap(ca, cb);
            }
            auto key = make_pair(a, b);
            auto it = con_index.find(key);
            int id;
            if (it == con_index.end()) {
                CompConstraint cc;
                cc.a = a;
                cc.b = b;
                cc.forbid.fill(0);
                for (auto& row : cc.weight) row.fill(0);
                id = comp_cons.size();
                comp_cons.push_back(cc);
                con_index[key] = id;
            } else {
                id = it->second;
            }
            comp_cons[id].forbid[ca] |= (uint16_t)(1u << cb);
            if (comp_cons[id].weight[ca][cb] < numeric_limits<uint16_t>::max() - w)
                comp_cons[id].weight[ca][cb] += w;
            else
                comp_cons[id].weight[ca][cb] = numeric_limits<uint16_t>::max();
        };

        for (const auto& con : constraints) {
            int ga = con.a, gb = con.b;
            int ca_root = group_comp[ga], cb_root = group_comp[gb];
            if (ca_root == cb_root) {
                for (int root_color = 0; root_color < width; ++root_color) {
                    int ca = comp_to_group[ga][root_color];
                    int cb = comp_to_group[gb][root_color];
                    if ((con.forbid[ca] >> cb) & 1u)
                        dom[ca_root] &= (uint16_t)~(1u << root_color);
                }
                if (dom[ca_root] == 0) return false;
                continue;
            }
            for (int ra = 0; ra < width; ++ra) {
                int ca = comp_to_group[ga][ra];
                uint16_t row = con.forbid[ca] & (uint16_t)((1u << width) - 1u);
                while (row) {
                    int cb = __builtin_ctz(row);
                    row &= (uint16_t)(row - 1);
                    int rb = group_to_comp[gb][cb];
                    int wt = con.weight[ca][cb] ? con.weight[ca][cb] : 1;
                    add_forbid(ca_root, cb_root, ra, rb, wt);
                }
            }
        }

        vector<vector<int>> comp_adj(nc);
        for (int i = 0; i < (int)comp_cons.size(); ++i) {
            comp_adj[comp_cons[i].a].push_back(i);
            comp_adj[comp_cons[i].b].push_back(i);
        }
        if (!propagate_comp_domains(dom, comp_cons)) return false;
        if (debug) {
            int fixed = 0;
            for (uint16_t d : dom) fixed += (__builtin_popcount(d) == 1);
            cerr << "bv perm comps=" << nc << " eq=" << eq_count << " neq=" << neq_count
                 << " comp_cons=" << comp_cons.size() << " fixed=" << fixed << "\n";
        }

        vector<int> comp_color;
        auto deadline = chrono::steady_clock::now() + chrono::seconds(nc > 2000 ? 70 : 45);
        if (!local_search_components(comp_cons, comp_adj, dom, comp_color, deadline, debug)) return false;

        vector<int> group_color(groups.size(), -1);
        for (int g = 0; g < (int)groups.size(); ++g) {
            int c = comp_color[group_comp[g]];
            group_color[g] = comp_to_group[g][c];
            if (group_color[g] < 0 || group_color[g] >= width) return false;
        }
        return build_model(group_color, model);
    }

    bool prove_contracted_clique_unsat(bool debug = false) {
        if (!recover_groups()) return false;
        if (!build_tables_and_domains()) return false;

        vector<char> is_eq(constraints.size(), 0), is_neq(constraints.size(), 0);
        vector<Perm> eq_perm(constraints.size()), neq_perm(constraints.size());
        int eq_count = 0, neq_count = 0;
        for (int i = 0; i < (int)constraints.size(); ++i) {
            if (allowed_permutation(constraints[i], eq_perm[i])) {
                is_eq[i] = 1;
                ++eq_count;
            } else if (forbidden_permutation(constraints[i], neq_perm[i])) {
                is_neq[i] = 1;
                ++neq_count;
            } else {
                return false;
            }
        }
        if (eq_count == 0 || neq_count == 0) return false;

        PermDSU dsu(*this, groups.size());
        for (int i = 0; i < (int)constraints.size(); ++i) {
            if (is_eq[i] && !dsu.unite(constraints[i].a, constraints[i].b, eq_perm[i]))
                return false;
        }

        map<int, int> root_to_comp;
        vector<int> group_comp(groups.size(), -1);
        for (int g = 0; g < (int)groups.size(); ++g) {
            auto fg = dsu.find(g);
            auto it = root_to_comp.find(fg.first);
            if (it == root_to_comp.end()) {
                int id = root_to_comp.size();
                it = root_to_comp.emplace(fg.first, id).first;
            }
            group_comp[g] = it->second;
        }
        int nc = root_to_comp.size();
        vector<set<int>> G(nc);
        for (int i = 0; i < (int)constraints.size(); ++i) {
            if (!is_neq[i]) continue;
            int a = group_comp[constraints[i].a];
            int b = group_comp[constraints[i].b];
            if (a == b) continue;
            G[a].insert(b);
            G[b].insert(a);
        }
        vector<int> clique = find_clique_at_least(G, width + 1);
        if ((int)clique.size() < width + 1) return false;
        clique.resize(width + 1);
        if (debug) cerr << "bv contracted clique comps=" << nc << " clique=" << clique.size()
                        << " eq=" << eq_count << " neq=" << neq_count << "\n";

        vector<char> in_comp(nc, 0), in_var(f.nvars + 1, 0);
        for (int c : clique) in_comp[c] = 1;
        int selected_groups = 0;
        for (int g = 0; g < (int)groups.size(); ++g) {
            if (!in_comp[group_comp[g]]) continue;
            ++selected_groups;
            for (int v : groups[g]) in_var[v] = 1;
        }

        Formula sub;
        sub.nvars = f.nvars;
        for (const auto& c : f.clauses) {
            bool keep = true;
            for (int lit : c) {
                int v = abs(lit);
                if (v < 1 || v > f.nvars || !in_var[v]) {
                    keep = false;
                    break;
                }
            }
            if (keep) sub.clauses.push_back(c);
        }
        if (debug) cerr << "bv contracted core groups=" << selected_groups
                        << " vars=" << count(in_var.begin(), in_var.end(), (char)1)
                        << " clauses=" << sub.clauses.size() << "\n";
        if (sub.clauses.empty() || sub.clauses.size() > 20000) return false;

        vector<int8_t> dummy;
        CDCL cdcl(sub, proof_path);
        cdcl.time_limit = 160.0;
        int res = cdcl.solve(dummy);
        return res == 20;
    }

    bool solve_sat(vector<int8_t>& model) {
        bool debug = getenv("BV_DEBUG") != nullptr;
        auto t0 = chrono::steady_clock::now();
        if (!recover_groups()) return false;
        if (debug) cerr << "bv recover groups=" << groups.size() << " width=" << width
                        << " t=" << chrono::duration<double>(chrono::steady_clock::now() - t0).count() << "\n";
        if (!build_tables_and_domains()) return false;
        if (debug) cerr << "bv build constraints=" << constraints.size()
                        << " table=" << table_edges.size()
                        << " t=" << chrono::duration<double>(chrono::steady_clock::now() - t0).count() << "\n";
        if (solve_permutation_coloring(model, debug)) {
            if (debug) cerr << "bv perm model ok t=" << chrono::duration<double>(chrono::steady_clock::now() - t0).count() << "\n";
            return true;
        }
        if (getenv("ENABLE_BV_CSP") == nullptr) return false;
        vector<int> color;
        if (!local_search(color, debug)) {
            if (debug) cerr << "bv search failed t=" << chrono::duration<double>(chrono::steady_clock::now() - t0).count() << "\n";
            return false;
        }
        if (debug) cerr << "bv search ok t=" << chrono::duration<double>(chrono::steady_clock::now() - t0).count() << "\n";
        bool ok = build_model(color, model);
        if (debug) cerr << "bv model " << ok << " t=" << chrono::duration<double>(chrono::steady_clock::now() - t0).count() << "\n";
        return ok;
    }
};

static bool try_bitonly_clique_unsat(const Formula& f, const string& proof_path) {
    int L = 0;
    for (const auto& c : f.clauses) {
        if (c.size() > 2) L = max(L, (int)c.size());
    }
    if (L == 0 || (L & 1)) return false;
    int b = L / 2;
    if (b < 2 || b > 4) return false;
    int max_width = 1 << b;

    map<vector<int>, int> support_count;
    for (const auto& c : f.clauses) {
        if ((int)c.size() != L) continue;
        vector<int> s;
        s.reserve(c.size());
        for (int lit : c) s.push_back(abs(lit));
        sort(s.begin(), s.end());
        s.erase(unique(s.begin(), s.end()), s.end());
        if ((int)s.size() != L) continue;
        support_count[s]++;
    }
    if (support_count.empty()) return false;
    map<int, int> count_hist;
    for (auto& kv : support_count) count_hist[kv.second]++;
    int width = 0, width_freq = 0;
    for (auto& kv : count_hist) {
        if (kv.second > width_freq) {
            width = kv.first;
            width_freq = kv.second;
        }
    }
    if (width < 3 || width > max_width) return false;
    if (width_freq * 2 < (int)support_count.size()) return false;

    vector<vector<int>> supports;
    supports.reserve(support_count.size());
    for (auto& kv : support_count) supports.push_back(kv.first);

    unordered_map<long long, int> pair_count;
    auto pkey = [](int a, int b) {
        if (a > b) swap(a, b);
        return (long long)a << 32 | (unsigned)b;
    };
    for (const auto& s : supports) {
        for (int i = 0; i < (int)s.size(); ++i)
            for (int j = i + 1; j < (int)s.size(); ++j)
                pair_count[pkey(s[i], s[j])]++;
    }

    vector<vector<pair<int,int>>> neigh(f.nvars + 1);
    for (auto& kv : pair_count) {
        if (kv.second <= width) continue;
        int a = (int)(kv.first >> 32);
        int z = (int)(kv.first & 0xffffffffu);
        if (a >= 1 && a <= f.nvars && z >= 1 && z <= f.nvars) {
            neigh[a].push_back({kv.second, z});
            neigh[z].push_back({kv.second, a});
        }
    }

    set<vector<int>> group_set;
    for (int v = 1; v <= f.nvars; ++v) {
        auto ns = neigh[v];
        if ((int)ns.size() < b - 1) continue;
        sort(ns.begin(), ns.end(), greater<pair<int,int>>());
        vector<int> g{v};
        for (int i = 0; i < b - 1; ++i) g.push_back(ns[i].second);
        sort(g.begin(), g.end());
        group_set.insert(g);
    }

    bool changed = true;
    while (changed) {
        changed = false;
        for (const auto& s : supports) {
            vector<vector<int>> combos;
            vector<int> cur;
            gen_combos_rec(s, b, 0, cur, combos);
            set<int> ss(s.begin(), s.end());
            for (const auto& comb : combos) {
                if (!group_set.count(comb)) continue;
                vector<int> rest;
                for (int v : s)
                    if (!binary_search(comb.begin(), comb.end(), v)) rest.push_back(v);
                if ((int)rest.size() == b && !group_set.count(rest)) {
                    group_set.insert(rest);
                    changed = true;
                }
            }
        }
    }

    vector<vector<int>> groups(group_set.begin(), group_set.end());
    if ((int)groups.size() < width + 1) return false;
    vector<int> var_group(f.nvars + 1, -1);
    for (int gi = 0; gi < (int)groups.size(); ++gi) {
        for (int v : groups[gi]) {
            if (var_group[v] != -1) return false;
            var_group[v] = gi;
        }
    }

    vector<set<int>> G(groups.size());
    for (const auto& s : supports) {
        set<int> gs;
        bool ok = true;
        for (int v : s) {
            int g = (v >= 1 && v <= f.nvars) ? var_group[v] : -1;
            if (g < 0) {
                ok = false;
                break;
            }
            gs.insert(g);
        }
        if (ok && gs.size() == 2) {
            auto it = gs.begin();
            int a = *it++;
            int z = *it;
            G[a].insert(z);
            G[z].insert(a);
        }
    }

    vector<int> clique = find_clique_at_least(G, width + 1);
    if ((int)clique.size() < width + 1) return false;
    clique.resize(width + 1);
    vector<char> in_clique_group(groups.size(), 0), in_var(f.nvars + 1, 0);
    for (int g : clique) {
        in_clique_group[g] = 1;
        for (int v : groups[g]) in_var[v] = 1;
    }

    Formula sub;
    sub.nvars = f.nvars;
    for (const auto& c : f.clauses) {
        bool keep = true;
        for (int lit : c) {
            int v = abs(lit);
            if (v < 1 || v > f.nvars || !in_var[v]) {
                keep = false;
                break;
            }
        }
        if (keep) sub.clauses.push_back(c);
    }
    if (sub.clauses.empty() || sub.clauses.size() > 2000) return false;

    vector<int8_t> dummy;
    CDCL cdcl(sub, proof_path);
    int res = cdcl.solve(dummy);
    return res == 20;
}

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 << "s UNKNOWN\n";
        return 0;
    }

    vector<int8_t> model;
    OneHotSolver oh(f, argv[2]);
    if (oh.detect()) {
        vector<int> clique = oh.max_clique();
        if ((int)clique.size() > oh.width && oh.write_clique_proof(clique)) {
            cout << "s UNSATISFIABLE\n";
            return 20;
        }
        if (oh.solve_sat(model)) {
            print_model(model);
            return 10;
        }
    }

    if (try_bitonly_clique_unsat(f, argv[2])) {
        cout << "s UNSATISFIABLE\n";
        return 20;
    }

    {
        BitVectorCSPSolver bv_unsat(f, argv[2]);
        if (bv_unsat.prove_contracted_clique_unsat(getenv("BV_DEBUG") != nullptr)) {
            cout << "s UNSATISFIABLE\n";
            return 20;
        }
    }

    {
        BitVectorCSPSolver bv(f, argv[2]);
        if (bv.solve_sat(model)) {
            print_model(model);
            return 10;
        }
    }

    CDCL cdcl(f, argv[2]);
    int res = cdcl.solve(model);
    if (res == 10) {
        print_model(model);
        return 10;
    }
    if (res == 20) {
        cout << "s UNSATISFIABLE\n";
        return 20;
    }
    cout << "s UNKNOWN\n";
    return 0;
}
