#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <numeric>
#include <chrono>
#include <random>
#include <unordered_map>
#include <functional>
#include <fstream>
#include <sstream>
#include <malloc.h>

using namespace std;
inline long get_rss_kb() {
    std::ifstream f("/proc/self/status"); std::string line;
    while(std::getline(f, line)) { if(line.substr(0,6) == "VmRSS:") return std::stol(line.substr(6)); }
    return 0;
}
static auto T0 = chrono::steady_clock::now();
double elapsed() { return chrono::duration<double>(chrono::steady_clock::now()-T0).count(); }
static constexpr double TLIM = 592.0;

// ======================= CSP =======================
using Dom = uint32_t;
inline int dsz(Dom d){return __builtin_popcount(d);}
inline int dfirst(Dom d){return __builtin_ctz(d);}

struct CSP {
    int n;
    int cnf_nv, cnf_nc;
    vector<vector<int>> svars;
    vector<pair<int,int>> vinfo;
    struct Con { int s1,s2; vector<Dom> f12, f21; };
    vector<Con> cons;
    struct NI{int nb,ci;bool is1;};
    vector<vector<NI>> adj;

    // Two-pass streaming parser: first pass collects ALO clauses and vinfo,
    // second pass processes binary negative clauses directly into CSP constraints.
    // This avoids storing all clauses simultaneously.
    bool parse_and_extract(const char* fn) {
        cnf_nv=0; cnf_nc=0; svars.clear(); vinfo.clear(); cons.clear(); adj.clear();
        vector<vector<int>> alo;
        {
            ifstream in(fn);
            if(!in) return false;
            string tok, rest;
            while(in >> tok) {
                if(tok == "c") { getline(in, rest); continue; }
                if(tok == "p") {
                    string fmt;
                    in >> fmt >> cnf_nv >> cnf_nc;
                    getline(in, rest);
                    continue;
                }
                int lit = stoi(tok);
                vector<int> cl;
                bool all_pos = true;
                while(lit != 0) {
                    cl.push_back(lit);
                    if(lit < 0) all_pos = false;
                    if(!(in >> lit)) return false;
                }
                if(!cl.empty() && all_pos) alo.push_back(std::move(cl));
            }
        }
        fprintf(stderr,"c CNF: %d vars, %d clauses (%.2fs)\n",cnf_nv,cnf_nc,elapsed());

        // Build station mapping
        n=alo.size(); svars.resize(n); vinfo.resize(cnf_nv+1,{-1,-1});
        for(int i=0;i<n;i++){auto sv=alo[i];sort(sv.begin(),sv.end());svars[i]=sv;
            for(int j=0;j<(int)sv.size();j++)vinfo[sv[j]]={i,j};}

        // Second pass: process binary negative clauses directly into constraints
        struct PH{size_t operator()(pair<int,int>p)const{return(size_t)p.first*100003+p.second;}};
        unordered_map<pair<int,int>,int,PH> pi;
        {
            ifstream in(fn);
            if(!in) return false;
            string tok, rest;
            while(in >> tok) {
                if(tok == "c") { getline(in, rest); continue; }
                if(tok == "p") { getline(in, rest); continue; }
                int lits[2]; int nlits=0; bool has_pos=false;
                int lit = stoi(tok);
                while(lit != 0) {
                    if(lit > 0) has_pos = true;
                    else if(nlits < 2) lits[nlits] = -lit;
                    if(lit < 0) nlits++;
                    if(!(in >> lit)) return false;
                }
                if(has_pos || nlits != 2) continue;
                int v1=lits[0], v2=lits[1];
                if(v1<1||v1>cnf_nv||v2<1||v2>cnf_nv) continue;
                auto[s1,i1]=vinfo[v1]; auto[s2,i2]=vinfo[v2];
                if(s1<0||s2<0||s1==s2) continue;
                int sa=min(s1,s2),sb=max(s1,s2),ia=(s1<s2)?i1:i2,ib=(s1<s2)?i2:i1;
                auto k=make_pair(sa,sb);auto it=pi.find(k);int idx;
                if(it==pi.end()){idx=cons.size();pi[k]=idx;Con c;c.s1=sa;c.s2=sb;
                    c.f12.assign(svars[sa].size(),0);c.f21.assign(svars[sb].size(),0);
                    cons.push_back(std::move(c));}else idx=it->second;
                cons[idx].f12[ia]|=(1u<<ib);cons[idx].f21[ib]|=(1u<<ia);
            }
        }

        adj.resize(n);
        for(int ci=0;ci<(int)cons.size();ci++){
            adj[cons[ci].s1].push_back({cons[ci].s2,ci,true});
            adj[cons[ci].s2].push_back({cons[ci].s1,ci,false});}
        fprintf(stderr,"c CSP: %d stations, %d constraints\n",n,(int)cons.size());
        return true;
    }
};

// ======================= AC-3 (for PAWS domain reduction) =======================
bool run_ac3(const CSP& csp, vector<Dom>& doms) {
    int n = csp.n;
    vector<bool> inq(n, false);
    queue<int> q;
    for(int i=0;i<n;i++){q.push(i);inq[i]=true;}
    while(!q.empty()) {
        int s = q.front(); q.pop(); inq[s]=false;
        for(auto& ni : csp.adj[s]) {
            auto& c = csp.cons[ni.ci];
            int nb = ni.nb;
            Dom od=doms[nb],nd=od,fd=doms[s];
            if(ni.is1){Dom ck=od;while(ck){int v=__builtin_ctz(ck);ck&=ck-1;if((fd&~c.f21[v])==0)nd&=~(1u<<v);}}
            else{Dom ck=od;while(ck){int v=__builtin_ctz(ck);ck&=ck-1;if((fd&~c.f12[v])==0)nd&=~(1u<<v);}}
            if(nd!=od){doms[nb]=nd;if(nd==0)return false;if(!inq[nb]){q.push(nb);inq[nb]=true;}}
        }
    }
    return true;
}

// ======================= UP-based propagation (for MAC proof) =======================
// Only propagates from singleton domains (equivalent to unit propagation on CNF)
bool propagate_up(const CSP& csp, int start, vector<Dom>& d) {
    queue<int> q;
    q.push(start);
    while(!q.empty()) {
        int s = q.front(); q.pop();
        if(dsz(d[s]) != 1) continue;
        int sval = dfirst(d[s]);
        for(auto& ni : csp.adj[s]) {
            auto& c = csp.cons[ni.ci];
            int nb = ni.nb;
            Dom od = d[nb];
            Dom conflicts = ni.is1 ? c.f12[sval] : c.f21[sval];
            Dom nd = od & ~conflicts;
            if(nd != od) {
                d[nb] = nd;
                if(nd == 0) return false;
                if(dsz(nd) == 1 && dsz(od) > 1) q.push(nb);
            }
        }
    }
    return true;
}

// ======================= Component Decomposition =======================
struct Components {
    int nc;
    vector<int> comp_id;
    vector<vector<int>> comp_stations;
    
    void find(const CSP& csp) {
        int n = csp.n;
        vector<int> parent(n); iota(parent.begin(),parent.end(),0);
        auto uf=[&](int x)->int{while(parent[x]!=x){parent[x]=parent[parent[x]];x=parent[x];}return x;};
        for(auto&c:csp.cons){int a=uf(c.s1),b=uf(c.s2);if(a!=b)parent[a]=b;}
        unordered_map<int,int> cmap; nc=0;
        comp_id.resize(n);
        for(int i=0;i<n;i++){int r=uf(i);auto it=cmap.find(r);
            if(it==cmap.end()){cmap[r]=nc;comp_id[i]=nc++;} else comp_id[i]=it->second;}
        comp_stations.resize(nc);
        for(int i=0;i<n;i++) comp_stations[comp_id[i]].push_back(i);
        sort(comp_stations.begin(),comp_stations.end(),
             [](const vector<int>&a,const vector<int>&b){return a.size()<b.size();});
        for(int i=0;i<nc;i++) for(int s:comp_stations[i]) comp_id[s]=i;
    }
};

// ======================= MAC with UP + Proof (trail-based) =======================
struct MACProof {
    const CSP& csp;
    long long nodes;
    bool timed_out;
    double time_limit;

    vector<int> assignment;

    // Proof entries: written directly to file to save memory
    vector<vector<pair<int,int>>> proof_entries; // Only used if proof_file is null
    FILE* proof_fp; // Direct file output
    int proof_line_count;

    bool collect_proof;
    MACProof(const CSP& c) : csp(c), nodes(0), timed_out(false), time_limit(600),
        proof_fp(nullptr), proof_line_count(0), collect_proof(true) {}

    // Trail-based UP: records changes so we can undo them
    struct Change { int station; Dom old_dom; };

    void write_proof_entry(const vector<pair<int,int>>& path, int extra_s = -1, int extra_v = -1) {
        if(!proof_fp) return;
        if(path.empty() && extra_s < 0) {
            fprintf(proof_fp, "rup >= 1 ;\n");
        } else {
            fprintf(proof_fp, "rup ");
            for(auto& [s, v] : path) {
                fprintf(proof_fp, "1 ~x%d ", csp.svars[s][v]);
            }
            if(extra_s >= 0) {
                fprintf(proof_fp, "1 ~x%d ", csp.svars[extra_s][extra_v]);
            }
            fprintf(proof_fp, ">= 1 ;\n");
        }
        proof_line_count++;
    }

    // Reusable queue buffer
    vector<int> qbuf;

    // Propagate from station 'start' (must be singleton), record changes on trail
    bool propagate_up_trail(vector<Dom>& d, int start, vector<Change>& trail) {
        int qh = 0;
        qbuf.clear();
        qbuf.push_back(start);
        while(qh < (int)qbuf.size()) {
            int s = qbuf[qh++];
            if(dsz(d[s]) != 1) continue;
            int sval = dfirst(d[s]);
            for(auto& ni : csp.adj[s]) {
                auto& c = csp.cons[ni.ci];
                int nb = ni.nb;
                Dom od = d[nb];
                Dom conflicts = ni.is1 ? c.f12[sval] : c.f21[sval];
                Dom nd = od & ~conflicts;
                if(nd != od) {
                    trail.push_back({nb, od});
                    d[nb] = nd;
                    if(nd == 0) return false;
                    if(dsz(nd) == 1 && dsz(od) > 1) qbuf.push_back(nb);
                }
            }
        }
        return true;
    }

    void undo_trail(vector<Dom>& d, vector<Change>& trail, int saved_size) {
        for(int i = (int)trail.size()-1; i >= saved_size; i--) {
            d[trail[i].station] = trail[i].old_dom;
        }
        trail.resize(saved_size);
    }

    // Returns: 1=SAT, -1=UNSAT, 0=timeout
    int solve(const vector<int>& comp, vector<Dom>& init_doms, double tlim) {
        time_limit = tlim;
        nodes = 0; timed_out = false;
        proof_entries.clear();

        int n = csp.n;
        vector<Dom> doms = init_doms;
        assignment.assign(n, -1);
        vector<Change> trail;
        trail.reserve(comp.size() * 32); // Pre-allocate to avoid reallocation
        qbuf.reserve(n); // Pre-allocate queue buffer

        // Initial UP: propagate from any singleton domains
        for(int s : comp) {
            if(dsz(doms[s]) == 1) {
                assignment[s] = dfirst(doms[s]);
                if(!propagate_up_trail(doms, s, trail)) return -1;
            }
        }

        vector<int> unassigned;
        for(int s : comp) if(assignment[s] < 0) unassigned.push_back(s);
        if(unassigned.empty()) return 1;

        vector<pair<int,int>> path;
        return search(unassigned, doms, path, trail);
    }

    int search(vector<int>& unassigned, vector<Dom>& doms, vector<pair<int,int>>& path,
               vector<Change>& trail) {
        if(elapsed() > time_limit) { timed_out = true; return 0; }
        if(unassigned.empty()) return 1;
        nodes++;

        // MRV: pick variable with smallest domain
        int best_idx = 0, best_sz = dsz(doms[unassigned[0]]), best_deg = csp.adj[unassigned[0]].size();
        for(int i=1;i<(int)unassigned.size();i++){
            int sz=dsz(doms[unassigned[i]]),deg=csp.adj[unassigned[i]].size();
            if(sz<best_sz||(sz==best_sz&&deg>best_deg)){best_sz=sz;best_deg=deg;best_idx=i;}
        }

        int s = unassigned[best_idx];
        swap(unassigned[best_idx], unassigned.back());
        unassigned.pop_back();

        Dom d = doms[s];
        Dom saved_dom_s = d;
        while(d) {
            int v = __builtin_ctz(d); d &= d-1;

            int trail_mark = trail.size();
            trail.push_back({s, doms[s]});
            doms[s] = 1u << v;
            assignment[s] = v;

            bool prop_ok = propagate_up_trail(doms, s, trail);

            if(prop_ok) {
                path.push_back({s, v});
                int result = search(unassigned, doms, path, trail);
                path.pop_back();

                if(result == 1) { unassigned.push_back(s); return 1; }
                if(timed_out) { undo_trail(doms, trail, trail_mark); assignment[s]=-1; unassigned.push_back(s); return 0; }
            } else {
                if(collect_proof) write_proof_entry(path, s, v);
            }

            undo_trail(doms, trail, trail_mark);
            assignment[s] = -1;
        }

        // All values of s exhausted
        if(collect_proof) write_proof_entry(path);

        unassigned.push_back(s);
        return -1;
    }
};

// ======================= Write VeriPB Proof =======================
bool write_proof(const char* proof_file, int cnf_nc, const CSP& csp,
                 const vector<vector<pair<int,int>>>& entries) {
    FILE* pf = fopen(proof_file, "w");
    if(!pf) return false;

    fprintf(pf, "pseudo-Boolean proof version 2.0\n");
    int next_id = cnf_nc + 1;
    
    for(auto& entry : entries) {
        if(entry.empty()) {
            // Empty path = final contradiction
            fprintf(pf, "rup >= 1 ;\n");
        } else {
            fprintf(pf, "rup ");
            for(auto& [s, v] : entry) {
                int dvar = csp.svars[s][v];
                fprintf(pf, "1 ~x%d ", dvar);
            }
            fprintf(pf, ">= 1 ;\n");
        }
        next_id++;
    }
    
    fprintf(pf, "output NONE\n");
    fprintf(pf, "conclusion UNSAT : %d\n", next_id - 1);
    fprintf(pf, "end pseudo-Boolean proof\n");
    fclose(pf);
    return true;
}

// ======================= PAWS Solver =======================
struct PAWS {
    const CSP& csp;
    const vector<Dom>& dom;
    int n, nc;
    vector<int> asgn;
    vector<int> w;
    vector<int> viol;
    vector<long long> sc;
    long long tc;
    int tv;
    vector<int> vl; vector<int> vp;
    mt19937 rng;
    
    vector<int> mac_assignment; // MAC-solved values (or -1)
    PAWS(const CSP& c, const vector<Dom>& d) : csp(c), dom(d), n(c.n), nc(c.cons.size()) {}

    struct Repair {
        const CSP& csp;
        const vector<Dom>& dom;
        const vector<int>& base;
        vector<int> sub, pos, val;
        vector<Dom> rd;
        long long nodes, node_limit;
        double deadline;

        struct Change { int idx; Dom old_dom; };
        vector<Change> trail;

        Repair(const CSP& c, const vector<Dom>& d, const vector<int>& b)
            : csp(c), dom(d), base(b), nodes(0), node_limit(0), deadline(0) {}

        bool setup(const vector<int>& vars) {
            sub = vars;
            pos.assign(csp.n, -1);
            for(int i=0;i<(int)sub.size();i++) pos[sub[i]] = i;
            rd.resize(sub.size());
            val.assign(sub.size(), -1);

            for(int ii=0; ii<(int)sub.size(); ii++) {
                int s = sub[ii];
                Dom d = dom[s];
                for(auto& ni : csp.adj[s]) {
                    if(pos[ni.nb] >= 0) continue;
                    int nbv = base[ni.nb];
                    if(nbv < 0) return false;
                    auto& con = csp.cons[ni.ci];
                    Dom bad = ni.is1 ? con.f12[0] : con.f21[0];
                    (void)bad;
                    Dom t = d;
                    while(t) {
                        int v = __builtin_ctz(t);
                        t &= t - 1;
                        bool conflict = ni.is1 ? ((con.f12[v] >> nbv) & 1)
                                                : ((con.f21[v] >> nbv) & 1);
                        if(conflict) d &= ~(1u << v);
                    }
                }
                if(d == 0) return false;
                rd[ii] = d;
            }
            return true;
        }

        void undo(int mark) {
            for(int i=(int)trail.size()-1; i>=mark; i--) rd[trail[i].idx] = trail[i].old_dom;
            trail.resize(mark);
        }

        bool assign_value(int idx, int v) {
            int s = sub[idx];
            val[idx] = v;
            Dom keep = 1u << v;
            if(rd[idx] != keep) {
                trail.push_back({idx, rd[idx]});
                rd[idx] = keep;
            }
            for(auto& ni : csp.adj[s]) {
                int j = pos[ni.nb];
                if(j < 0) continue;
                auto& con = csp.cons[ni.ci];
                if(val[j] >= 0) {
                    bool conflict = ni.is1 ? ((con.f12[v] >> val[j]) & 1)
                                            : ((con.f21[v] >> val[j]) & 1);
                    if(conflict) return false;
                    continue;
                }
                Dom bad = ni.is1 ? con.f12[v] : con.f21[v];
                Dom nd = rd[j] & ~bad;
                if(nd != rd[j]) {
                    trail.push_back({j, rd[j]});
                    rd[j] = nd;
                    if(nd == 0) return false;
                }
            }
            return true;
        }

        bool dfs(int left) {
            if(left == 0) return true;
            if(++nodes > node_limit || elapsed() > deadline) return false;

            int bi = -1, bsz = 1000, bdeg = -1;
            for(int i=0;i<(int)sub.size();i++) {
                if(val[i] >= 0) continue;
                int sz = dsz(rd[i]);
                int deg = csp.adj[sub[i]].size();
                if(sz < bsz || (sz == bsz && deg > bdeg)) {
                    bi = i; bsz = sz; bdeg = deg;
                    if(sz == 1) break;
                }
            }
            if(bi < 0) return true;

            int s = sub[bi];
            int bval = base[s];
            int vals[32], cnt = 0;
            if(bval >= 0 && (rd[bi] & (1u << bval))) vals[cnt++] = bval;
            Dom t = rd[bi] & (bval >= 0 ? ~(1u << bval) : ~0u);
            while(t) {
                int v = __builtin_ctz(t);
                t &= t - 1;
                vals[cnt++] = v;
            }

            for(int oi=0; oi<cnt; oi++) {
                int v = vals[oi];
                int mark = trail.size();
                if(assign_value(bi, v) && dfs(left - 1)) return true;
                val[bi] = -1;
                undo(mark);
            }
            return false;
        }

        bool solve(const vector<int>& vars, vector<int>& out, long long nodes_cap, double tcap) {
            if(!setup(vars)) return false;
            nodes = 0;
            node_limit = nodes_cap;
            deadline = min(tcap, elapsed() + 8.0);
            trail.clear();
            if(!dfs((int)sub.size())) return false;
            out = base;
            for(int i=0;i<(int)sub.size();i++) out[sub[i]] = val[i];
            return true;
        }
    };

    bool try_repair_best(const vector<int>& seed_asgn, int seed_tv, double tlim, vector<int>& repaired) {
        if(seed_tv <= 0 || seed_tv > 4 || elapsed() > tlim - 1.0) return false;

        vector<int> bad_cons;
        for(int ci=0; ci<nc; ci++) {
            auto& con = csp.cons[ci];
            if((con.f12[seed_asgn[con.s1]] >> seed_asgn[con.s2]) & 1) bad_cons.push_back(ci);
        }
        if(bad_cons.empty()) { repaired = seed_asgn; return true; }

        vector<char> mark(n, 0), frontier(n, 0);
        vector<int> vars, fr;
        auto add_var = [&](int s, vector<int>* to_frontier) {
            if(!mark[s]) { mark[s] = 1; vars.push_back(s); }
            if(to_frontier && !frontier[s]) { frontier[s] = 1; to_frontier->push_back(s); }
        };
        for(int ci : bad_cons) {
            add_var(csp.cons[ci].s1, &fr);
            add_var(csp.cons[ci].s2, &fr);
        }

        int max_radius = (seed_tv <= 2) ? 2 : 1;
        bool dbg = getenv("REPAIR_DEBUG") != nullptr;
        for(int radius=0; radius<=max_radius; radius++) {
            if(dbg) fprintf(stderr, "c   repair-debug: seed=%d radius=%d vars=%zu\n",
                            seed_tv, radius, vars.size());
            if(!vars.empty() && vars.size() <= 160) {
                vector<int> attempt = vars;
                sort(attempt.begin(), attempt.end(), [&](int a, int b) {
                    return csp.adj[a].size() > csp.adj[b].size();
                });
                Repair rep(csp, dom, seed_asgn);
                long long cap = (attempt.size() <= 48) ? 3000000LL :
                                (attempt.size() <= 96 ? 1200000LL : 450000LL);
                if(rep.solve(attempt, repaired, cap, tlim)) {
                    fprintf(stderr, "c   repair: fixed %d violations with %zu vars, %lld nodes\n",
                            seed_tv, attempt.size(), rep.nodes);
                    return true;
                }
            }

            if(radius == max_radius || vars.size() > 160) break;
            vector<int> next;
            for(int s : fr) frontier[s] = 0;
            for(int s : fr) {
                for(auto& ni : csp.adj[s]) add_var(ni.nb, &next);
            }
            fr.swap(next);
        }
        return false;
    }
    
    void rebuild() {
        tc=0; tv=0; sc.assign(n,0);
        vl.clear(); vp.assign(nc,-1); viol.assign(nc,0);
        for(int ci=0;ci<nc;ci++){
            auto&c=csp.cons[ci];
            if((c.f12[asgn[c.s1]]>>asgn[c.s2])&1){
                viol[ci]=1;tv++;tc+=w[ci];sc[c.s1]+=w[ci];sc[c.s2]+=w[ci];
                vp[ci]=vl.size();vl.push_back(ci);
            }
        }
    }
    void init_greedy(bool randomize=false, bool use_mac=false) {
        asgn.assign(n,-1); w.assign(nc,1);
        // Pre-assign MAC-solved stations
        if(use_mac && (int)mac_assignment.size()==n) {
            for(int i=0;i<n;i++) if(mac_assignment[i]>=0) asgn[i]=mac_assignment[i];
        }
        vector<int> ord;
        for(int i=0;i<n;i++) if(asgn[i]<0) ord.push_back(i);
        if(randomize){for(int i=ord.size()-1;i>0;i--)swap(ord[i],ord[rng()%(i+1)]);
            sort(ord.begin(),ord.end(),[&](int a,int b){return csp.adj[a].size()>csp.adj[b].size();});}
        else sort(ord.begin(),ord.end(),[&](int a,int b){return csp.adj[a].size()>csp.adj[b].size();});
        for(int s:ord){Dom d=dom[s];int bv=dfirst(d),bc=n+1;
            Dom t=d;while(t){int v=__builtin_ctz(t);t&=t-1;
                int cost=0;for(auto&ni:csp.adj[s]){if(asgn[ni.nb]<0)continue;auto&c=csp.cons[ni.ci];
                    int nbv=asgn[ni.nb];if(ni.is1){if((c.f12[v]>>nbv)&1)cost++;}
                    else{if((c.f21[v]>>nbv)&1)cost++;}}
                if(cost<bc||(cost==bc&&(rng()&1))){bc=cost;bv=v;}}
            asgn[s]=bv;}
        rebuild();
    }
    void init_random() {
        asgn.assign(n,0); w.assign(nc,1);
        for(int i=0;i<n;i++){Dom d=dom[i];int sz=dsz(d),pk=rng()%sz;
            Dom t=d;for(int k=0;k<pk;k++)t&=t-1;asgn[i]=__builtin_ctz(t);}
        rebuild();
    }
    void apply_move(int s, int nval) {
        int oval=asgn[s];
        for(auto&ni:csp.adj[s]){int ci=ni.ci;auto&c=csp.cons[ci];int nbv=asgn[ni.nb];
            bool was,now;
            if(ni.is1){was=(c.f12[oval]>>nbv)&1;now=(c.f12[nval]>>nbv)&1;}
            else{was=(c.f21[oval]>>nbv)&1;now=(c.f21[nval]>>nbv)&1;}
            if(was&&!now){viol[ci]=0;tv--;tc-=w[ci];sc[s]-=w[ci];sc[ni.nb]-=w[ci];
                int pos=vp[ci];int last=vl.back();vl[pos]=last;vp[last]=pos;vl.pop_back();vp[ci]=-1;}
            else if(!was&&now){viol[ci]=1;tv++;tc+=w[ci];sc[s]+=w[ci];sc[ni.nb]+=w[ci];
                vp[ci]=vl.size();vl.push_back(ci);}
        }
        asgn[s]=nval;
    }
    long long delta_cost(int s, int nval) {
        int oval=asgn[s]; long long d=0;
        for(auto&ni:csp.adj[s]){int ci=ni.ci;auto&c=csp.cons[ci];int nbv=asgn[ni.nb];
            bool was,now;
            if(ni.is1){was=(c.f12[oval]>>nbv)&1;now=(c.f12[nval]>>nbv)&1;}
            else{was=(c.f21[oval]>>nbv)&1;now=(c.f21[nval]>>nbv)&1;}
            if(was&&!now) d-=w[ci]; else if(!was&&now) d+=w[ci];}
        return d;
    }
    // Try compound move: change both stations of a constraint simultaneously
    bool try_compound(int ci) {
        auto& con = csp.cons[ci];
        int s1=con.s1, s2=con.s2;
        int ov1=asgn[s1], ov2=asgn[s2];
        Dom d1=dom[s1], d2=dom[s2];
        int best_v1=-1, best_v2=-1;
        long long best_dc = 0; // must improve

        // Pre-compute delta for s1 changes (excluding constraint ci)
        Dom t1=d1;
        while(t1){int v1=__builtin_ctz(t1);t1&=t1-1;
            if(v1==ov1)continue;
            long long dc1 = delta_cost(s1,v1); // includes ci effect with current s2
            Dom t2=d2;
            while(t2){int v2=__builtin_ctz(t2);t2&=t2-1;
                if(v2==ov2)continue;
                long long dc2 = delta_cost(s2,v2); // includes ci effect with current s1
                // Adjust for double-counting of constraint ci
                // dc1 includes effect of (ov1->v1) on ci with current ov2
                // dc2 includes effect of (ov2->v2) on ci with current ov1
                // But we're changing both: need effect of (v1,v2) on ci
                bool was = (con.f12[ov1]>>ov2)&1;
                bool now = (con.f12[v1]>>v2)&1;
                bool dc1_ci_was = (con.f12[ov1]>>ov2)&1;
                bool dc1_ci_now = (con.f12[v1]>>ov2)&1;
                bool dc2_ci_was = (con.f12[ov1]>>ov2)&1;
                bool dc2_ci_now = (con.f12[ov1]>>v2)&1;
                long long dc = dc1 + dc2;
                // Remove individual ci contributions, add combined
                if(dc1_ci_was&&!dc1_ci_now) dc += w[ci]; // dc1 subtracted w[ci], undo
                else if(!dc1_ci_was&&dc1_ci_now) dc -= w[ci]; // dc1 added w[ci], undo
                if(dc2_ci_was&&!dc2_ci_now) dc += w[ci];
                else if(!dc2_ci_was&&dc2_ci_now) dc -= w[ci];
                // Add actual combined effect
                if(was&&!now) dc -= w[ci];
                else if(!was&&now) dc += w[ci];
                if(dc<best_dc){best_dc=dc;best_v1=v1;best_v2=v2;}
            }
        }
        if(best_v1>=0) {
            apply_move(s1, best_v1);
            apply_move(s2, best_v2);
            return true;
        }
        return false;
    }

    int solve(double tlim) {
        int best_tv=n+1; vector<int> best_asgn; long long total_steps=0;
        for(int run=0;elapsed()<tlim;run++){
            if(run==0)init_greedy(false, true);
            else if(best_tv>0&&best_tv<=5){
                asgn=best_asgn;w.assign(nc,1);rebuild();
                // Focused perturbation near violations
                vector<bool> near(n,false);
                for(int ci:vl){near[csp.cons[ci].s1]=true;near[csp.cons[ci].s2]=true;}
                // Expand neighborhood
                int expand = (best_tv <= 2) ? 1 : 2;
                for(int e=0;e<expand;e++){
                    vector<bool> near2=near;
                    for(int i=0;i<n;i++)if(near2[i])for(auto&ni:csp.adj[i])near[ni.nb]=true;
                }
                vector<int> cands;for(int i=0;i<n;i++)if(near[i])cands.push_back(i);
                // Perturb fewer when close to solution
                int nperturb = (best_tv <= 2) ? min((int)cands.size(), max(3, n/50))
                                               : min((int)cands.size(), max(5, n/20));
                for(int i=cands.size()-1;i>0;i--)swap(cands[i],cands[rng()%(i+1)]);
                for(int i=0;i<nperturb;i++){int s=cands[i];Dom d=dom[s];int sz=dsz(d),pk=rng()%sz;
                    Dom t=d;for(int k=0;k<pk;k++)t&=t-1;apply_move(s,__builtin_ctz(t));}
            } else { if(rng()%4==0)init_greedy(run>0);else init_random(); }
            if(run==0)fprintf(stderr,"c   Init: %d violations\n",tv);
            if(tv<best_tv){
                best_tv=tv;best_asgn=asgn;
                vector<int> repaired;
                if(try_repair_best(best_asgn, best_tv, tlim, repaired)) {
                    asgn = repaired; rebuild();
                    best_tv = tv; best_asgn = asgn;
                    if(tv == 0) return 1;
                }
            }
            if(tv==0)return 1;
            int no_imp=0,max_no_imp=(best_tv<=3)?n*600:n*300;
            while(no_imp<max_no_imp&&elapsed()<tlim){
                if(tv==0)return 1;
                total_steps++;no_imp++;
                int ci=vl[rng()%vl.size()];auto&con=csp.cons[ci];
                int stations[2]={con.s1,con.s2};int best_s=-1,best_v=-1;long long best_dc=1LL<<60;
                for(int si=0;si<2;si++){int s=stations[si];Dom d=dom[s];int oval=asgn[s];
                    Dom t=d;while(t){int v=__builtin_ctz(t);t&=t-1;if(v==oval)continue;
                        long long dc=delta_cost(s,v);
                        if(dc<best_dc){best_dc=dc;best_s=s;best_v=v;}
                        else if(dc==best_dc&&(rng()&1)){best_s=s;best_v=v;}}}
                if(best_s>=0&&best_dc<0){apply_move(best_s,best_v);
                    if(tv<best_tv){
                        best_tv=tv;best_asgn=asgn;no_imp=0;
                        vector<int> repaired;
                        if(try_repair_best(best_asgn, best_tv, tlim, repaired)) {
                            asgn = repaired; rebuild();
                            best_tv = tv; best_asgn = asgn;
                            if(tv == 0) return 1;
                        }
                    }}
                else{
                    // Try compound move before weighting
                    bool compound_ok = false;
                    if((rng()%4)==0 && dsz(dom[con.s1])<=8 && dsz(dom[con.s2])<=8) {
                        compound_ok = try_compound(ci);
                    }
                    if(compound_ok) {
                        if(tv<best_tv){
                            best_tv=tv;best_asgn=asgn;no_imp=0;
                            vector<int> repaired;
                            if(try_repair_best(best_asgn, best_tv, tlim, repaired)) {
                                asgn = repaired; rebuild();
                                best_tv = tv; best_asgn = asgn;
                                if(tv == 0) return 1;
                            }
                        }
                    } else {
                        // Weight all violated constraints
                        for(int vi:vl){w[vi]++;tc++;sc[csp.cons[vi].s1]++;sc[csp.cons[vi].s2]++;}
                        if((rng()%100)<5){int s=stations[rng()%2];Dom d=dom[s];int sz=dsz(d),pk=rng()%sz;
                            Dom t=d;for(int k=0;k<pk;k++)t&=t-1;int nv=__builtin_ctz(t);
                            if(nv!=asgn[s])apply_move(s,nv);}
                    }
                }
                if((rng()%1000)<1){tc=0;sc.assign(n,0);
                    for(int ci2=0;ci2<nc;ci2++){if(w[ci2]>1)w[ci2]--;
                        if(viol[ci2]){tc+=w[ci2];sc[csp.cons[ci2].s1]+=w[ci2];sc[csp.cons[ci2].s2]+=w[ci2];}}}
                if(total_steps%5000000==0)fprintf(stderr,"c   run=%d steps=%lldM viol=%d best=%d (%.1fs)\n",
                    run,total_steps/1000000,tv,best_tv,elapsed());
            }
        }
        asgn=best_asgn;tv=best_tv;
        fprintf(stderr,"c   Done: best=%d steps=%lldM\n",best_tv,total_steps/1000000);
        return tv==0?1:0;
    }
};

// ======================= Utilities =======================
// Verify assignment against CSP (equivalent to CNF verification)
bool verify_csp(const CSP& csp, const vector<int>& a) {
    for(int i=0;i<csp.n;i++){
        if(a[i]<0||(int)a[i]>=(int)csp.svars[i].size()) return false;
    }
    for(auto&c:csp.cons){
        if((c.f12[a[c.s1]]>>a[c.s2])&1) return false;
    }
    return true;
}

bool verify_cnf_file(const char* fn, int cnf_nv, const CSP& csp, const vector<int>& a) {
    vector<unsigned char> val(cnf_nv + 1, 0);
    for(int v=1; v<=cnf_nv; v++) {
        auto [s,i] = csp.vinfo[v];
        val[v] = (s >= 0 && s < (int)a.size() && a[s] == i) ? 1 : 0;
    }
    ifstream in(fn);
    if(!in) return false;
    string line;
    while(getline(in, line)) {
        if(line.empty() || line[0] == 'c' || line[0] == 'p') continue;
        istringstream ss(line);
        int lit; bool sat = false, saw = false;
        while(ss >> lit) {
            if(lit == 0) break;
            saw = true;
            int v = lit > 0 ? lit : -lit;
            if(v < 1 || v > cnf_nv) return false;
            bool tv = val[v] != 0;
            if((lit > 0 && tv) || (lit < 0 && !tv)) sat = true;
        }
        if(saw && !sat) return false;
    }
    return true;
}

void print_model(int cnf_nv, const CSP& csp, const vector<int>& a) {
    printf("s SATISFIABLE\nv ");int c=0;
    for(int v=1;v<=cnf_nv;v++){auto[s,i]=csp.vinfo[v];bool val=(s>=0&&a[s]==i);
        if(c>0&&c%20==0)printf("\nv ");printf("%d ",val?v:-v);c++;}
    printf("0\n");
}

int main(int argc, char** argv) {
    if(argc!=3){fprintf(stderr,"usage: solver <formula.cnf> <out.pbp>\n");return 1;}
    fprintf(stderr,"c FCC Solver v16\n");
    const char* proof_file = argv[2];
    CSP csp;
    if(!csp.parse_and_extract(argv[1])){printf("s UNKNOWN\n");return 0;}
    int cnf_nc = csp.cnf_nc, cnf_nv = csp.cnf_nv;
    
    // AC for domain reduction (used by PAWS, not for proof)
    vector<Dom> domains(csp.n);
    for(int i=0;i<csp.n;i++) domains[i]=(1u<<csp.svars[i].size())-1;
    vector<Dom> orig_domains = domains; // Keep originals for MAC proof
    if(!run_ac3(csp,domains)){fprintf(stderr,"c AC: UNSAT\n");
        // Use MAC on original domains for proof
        domains = orig_domains;
    }
    int fixed=0;for(int i=0;i<csp.n;i++)if(dsz(domains[i])==1)fixed++;
    fprintf(stderr,"c After AC: %d/%d fixed (%.2fs)\n",fixed,csp.n,elapsed());
    
    // Component decomposition
    Components comps;
    comps.find(csp);
    fprintf(stderr,"c Components: %d\n", comps.nc);
    
    // Try MAC (with UP propagation) on each component for UNSAT detection
    vector<int> global_assignment(csp.n, -1);
    bool found_unsat = false;
    vector<int> unsolved_comps;
    
    double mac_deadline = min(elapsed() + 90.0, TLIM - 120.0);

    for(int ci=0; ci<comps.nc && !found_unsat && elapsed() < mac_deadline; ci++) {
        auto& comp = comps.comp_stations[ci];
        if(comp.size() <= 1) {
            global_assignment[comp[0]] = dfirst(orig_domains[comp[0]]);
            continue;
        }

        // Smaller components are more likely UNSAT cores - give them more time
        double per_comp = (comp.size() <= 200) ? 20.0 : 3.0;
        double comp_tlim = min(mac_deadline, elapsed() + per_comp);
        
        int result;
        long long mac_nodes;
        {
            // First run: NO proof collection (saves memory)
            MACProof mac(csp);
            mac.collect_proof = false;
            vector<Dom> mac_doms = orig_domains;
            result = mac.solve(comp, mac_doms, comp_tlim);
            mac_nodes = mac.nodes;

            if(result == 1) {
                for(int s : comp) global_assignment[s] = mac.assignment[s];
            } else if(result == 0) {
                unsolved_comps.push_back(ci);
            }
        }
        if(mac_nodes > 10000) malloc_trim(0);

        if(result == -1) {
            fprintf(stderr,"c Component %d (%d stations): UNSAT (%lld nodes, %.2fs)\n",
                    ci, (int)comp.size(), mac_nodes, elapsed());
            // Re-run WITH proof written directly to file (saves memory)
            if(mac_nodes <= 2500000) {
                FILE* pf = fopen(proof_file, "w");
                if(pf) {
                    fprintf(pf, "pseudo-Boolean proof version 2.0\n");
                    MACProof mac2(csp);
                    mac2.collect_proof = true;
                    mac2.proof_fp = pf;
                    vector<Dom> mac_doms2 = orig_domains;
                    double proof_tlim = min(TLIM - 5.0, elapsed() + 180.0);
                    int r2 = mac2.solve(comp, mac_doms2, proof_tlim);
                    if(r2 == -1) {
                        // Write final contradiction
                        fprintf(pf, "rup >= 1 ;\n");
                        int final_id = cnf_nc + mac2.proof_line_count + 1;
                        fprintf(pf, "output NONE\n");
                        fprintf(pf, "conclusion UNSAT : %d\n", final_id);
                        fprintf(pf, "end pseudo-Boolean proof\n");
                        fclose(pf);
                        printf("s UNSATISFIABLE\n");
                        return 20;
                    }
                    fclose(pf);
                }
            }
            // If proof failed, still report UNKNOWN
            found_unsat = true;
            malloc_trim(0);
        }
        if(result == 0) {
            fprintf(stderr,"c Component %d (%d stations): MAC timeout (%lld nodes, %.2fs)\n",
                    ci, (int)comp.size(), mac_nodes, elapsed());
        }
    }
    
    if(found_unsat) {
        // Proof was already written during MAC re-run if possible
        // If we get here, proof was not available
        fprintf(stderr,"c UNSAT detected but no proof (%.2fs)\n", elapsed());
        printf("s UNKNOWN\n");
        return 0;
    }
    
    // Check if all components solved by MAC
    if(unsolved_comps.empty()) {
        fprintf(stderr,"c All components solved by MAC (%.2fs)\n", elapsed());
        if(verify_csp(csp, global_assignment) && verify_cnf_file(argv[1], cnf_nv, csp, global_assignment)) {
            fprintf(stderr,"c Verified\n");
            print_model(cnf_nv, csp, global_assignment);
            return 10;
        }
        fprintf(stderr,"c MAC verification FAILED\n");
    }
    
    // Run PAWS on remaining time (use AC-reduced domains)
    malloc_trim(0); // Release freed MAC memory back to OS
    fprintf(stderr,"c Running PAWS (%.2fs)\n", elapsed());
    PAWS paws(csp, domains);
    paws.rng.seed(42);
    paws.mac_assignment = global_assignment; // Pass MAC-solved values for initialization
    int result = paws.solve(TLIM - 2.0);
    if(result == 1) {
        fprintf(stderr,"c SAT (%.2fs)\n", elapsed());
        if(verify_csp(csp, paws.asgn) && verify_cnf_file(argv[1], cnf_nv, csp, paws.asgn)) {
            fprintf(stderr,"c Verified\n");
            print_model(cnf_nv, csp, paws.asgn);
            return 10;
        }
        fprintf(stderr,"c Verification FAILED\n");
    }
    
    printf("s UNKNOWN\n"); return 0;
}
