// High-performance CDCL SAT Solver with VeriPB proof logging & clause deletion
// Written from scratch for pebbling instance family
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <vector>
#include <cmath>
#include <climits>
#include <cstdint>
#include <sys/time.h>

using namespace std;

static double wallTime() {
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    return tv.tv_sec + tv.tv_usec*1e-6;
}
static double t0;
static constexpr double TL = 540.0;
static inline bool timeout() { return wallTime()-t0 > TL; }

// ============ Literal encoding ============
static inline int mkLit(int v, bool neg) { return v*2+neg; }
static inline int litNot(int l) { return l^1; }
static inline int litVar(int l) { return l>>1; }
static inline bool litSign(int l) { return l&1; }
static inline int fromDimacs(int x) { return (abs(x)-1)*2+(x<0); }

// ============ Clause arena ============
static uint32_t* ca;
static int caSz, caCap;

struct CRef { int ofs; };
static const CRef CRef_Undef = {-1};

// Layout for original: [header][lits...]
// Layout for learnt:   [header][lbd|proofId_high16][proofId_low32][lits...]
// header bits 0-29 = size, bit 30 = learnt

static inline int clauseSize(CRef cr) { return ca[cr.ofs] & 0x3FFFFFFF; }
static inline bool clauseLearnt(CRef cr) { return (ca[cr.ofs] >> 30) & 1; }
static inline int* clauseLits(CRef cr) {
    return (int*)&ca[cr.ofs + 1 + (clauseLearnt(cr) ? 2 : 0)];
}
static inline uint16_t& clauseLBD(CRef cr) { return ((uint16_t*)&ca[cr.ofs+1])[1]; }
static inline int& clauseProofId(CRef cr) { return *(int*)&ca[cr.ofs + 2]; }

static CRef allocClause(const int* lits, int sz, bool learnt, int lbd = 0, int proofId = 0) {
    int need = 1 + sz + (learnt ? 2 : 0);
    while (caSz + need > caCap) {
        caCap = max(caCap*2, caSz+need+1024);
        ca = (uint32_t*)realloc(ca, sizeof(uint32_t)*caCap);
    }
    CRef cr = {caSz};
    ca[caSz] = ((uint32_t)learnt << 30) | (uint32_t)sz;
    if (learnt) {
        ca[caSz+1] = 0;
        clauseLBD(cr) = (uint16_t)lbd;
        clauseProofId(cr) = proofId;
    }
    caSz += 1 + (learnt ? 2 : 0);
    memcpy(&ca[caSz], lits, sizeof(int)*sz);
    caSz += sz;
    return cr;
}

// ============ Globals ============
static int nVars, nOrigClauses;
static vector<CRef> origClauses;
static vector<CRef> learnts;

struct Watcher { CRef cref; int blocker; };
static vector<Watcher>* watches;

static uint8_t* assigns;
static int* levels;
static CRef* reasons;
static int* trail;
static int trailSz;
static int* trailLim;
static int nLevels;
static int qhead;

static double* activity;
static double varInc;
static constexpr double varDecay = 0.85;

static int* heap;
static int heapSz;
static int* heapIdx;

static void heapSwap(int i, int j) {
    int a = heap[i], b = heap[j];
    heap[i] = b; heap[j] = a;
    heapIdx[b] = i; heapIdx[a] = j;
}
static void heapUp(int i) {
    while (i > 0) {
        int p = (i-1)/2;
        if (activity[heap[i]] <= activity[heap[p]]) break;
        heapSwap(i,p); i = p;
    }
}
static void heapDown(int i) {
    while (2*i+1 < heapSz) {
        int c = 2*i+1;
        if (c+1<heapSz && activity[heap[c+1]]>activity[heap[c]]) c++;
        if (activity[heap[i]]>=activity[heap[c]]) break;
        heapSwap(i,c); i = c;
    }
}
static void heapInsert(int v) {
    if (heapIdx[v]>=0) return;
    heap[heapSz]=v; heapIdx[v]=heapSz; heapSz++;
    heapUp(heapIdx[v]);
}
static int heapPop() {
    int v=heap[0]; heapIdx[v]=-1;
    if (--heapSz>0) { heap[0]=heap[heapSz]; heapIdx[heap[0]]=0; heapDown(0); }
    return v;
}
static void heapUpdate(int v) { if (heapIdx[v]>=0) heapUp(heapIdx[v]); }

static int8_t* polarity;
static bool* seen;
static uint32_t* lbdStamp;
static uint32_t lbdCounter;

// Proof
static FILE* proofFp;
static bool proofOn;
static int proofId;

// Buffered proof output
static char* proofBuf;
static int proofBufSz;
static const int proofBufCap = 1 << 20; // 1MB buffer

static void proofFlush() {
    if (proofBufSz > 0 && proofFp) {
        fwrite(proofBuf, 1, proofBufSz, proofFp);
        proofBufSz = 0;
    }
}

static void proofWrite(const char* s, int len) {
    if (proofBufSz + len > proofBufCap) proofFlush();
    if (len > proofBufCap) { fwrite(s, 1, len, proofFp); return; }
    memcpy(proofBuf + proofBufSz, s, len);
    proofBufSz += len;
}

// Fast int to string
static int intToStr(int v, char* buf) {
    if (v == 0) { buf[0] = '0'; return 1; }
    bool neg = v < 0;
    if (neg) v = -v;
    char tmp[12]; int len = 0;
    while (v > 0) { tmp[len++] = '0' + v%10; v /= 10; }
    int pos = 0;
    if (neg) buf[pos++] = '-';
    for (int i = len-1; i >= 0; i--) buf[pos++] = tmp[i];
    return pos;
}

static int proofWriteClause(const int* lits, int sz) {
    if (!proofFp) return 0;
    proofId++;
    char line[sz * 20 + 20];
    int pos = 0;
    line[pos++] = 'u'; line[pos++] = ' ';
    for (int i = 0; i < sz; i++) {
        line[pos++] = '1'; line[pos++] = ' ';
        if (litSign(lits[i])) { line[pos++] = '~'; }
        line[pos++] = 'x';
        pos += intToStr(litVar(lits[i])+1, line+pos);
        line[pos++] = ' ';
    }
    line[pos++] = '>'; line[pos++] = '='; line[pos++] = ' ';
    line[pos++] = '1'; line[pos++] = ' ';
    line[pos++] = ';'; line[pos++] = '\n';
    proofWrite(line, pos);
    return proofId;
}

static void proofDelete(int id) {
    if (!proofFp) return;
    char line[30];
    int pos = 0;
    line[pos++]='d'; line[pos++]='e'; line[pos++]='l'; line[pos++]=' ';
    line[pos++]='i'; line[pos++]='d'; line[pos++]=' ';
    pos += intToStr(id, line+pos);
    line[pos++] = ' '; line[pos++] = ';'; line[pos++] = '\n';
    proofWrite(line, pos);
}

// ============ Helpers ============
static inline uint8_t litVal(int l) {
    uint8_t a = assigns[l>>1];
    if (a == 0) return 0;
    return (l&1) ? (3-a) : a;
}

static inline void assign(int lit, CRef from) {
    int v = lit>>1;
    assigns[v] = (lit&1) ? 2 : 1;
    levels[v] = nLevels;
    reasons[v] = from;
    trail[trailSz++] = lit;
}

static void bumpVar(int v) {
    activity[v] += varInc;
    if (activity[v] > 1e100) {
        for (int i = 0; i < nVars; i++) activity[i] *= 1e-100;
        varInc *= 1e-100;
    }
    heapUpdate(v);
}

// ============ Propagation ============
static CRef propagate() {
    while (qhead < trailSz) {
        int p = trail[qhead++];
        vector<Watcher>& ws = watches[p];
        Watcher* beg = ws.data();
        Watcher* end = beg + ws.size();
        Watcher* i = beg;
        Watcher* j = beg;
        
        while (i < end) {
            if (litVal(i->blocker) == 1) { *j++ = *i++; continue; }
            
            CRef cr = i->cref;
            int* lits = clauseLits(cr);
            int falseLit = litNot(p);
            if (lits[0] == falseLit) { lits[0] = lits[1]; lits[1] = falseLit; }
            
            Watcher newW = {cr, lits[0]};
            if (litVal(lits[0]) == 1) { *j++ = newW; i++; continue; }
            
            int sz = clauseSize(cr);
            bool found = false;
            for (int k = 2; k < sz; k++) {
                if (litVal(lits[k]) != 2) {
                    lits[1] = lits[k]; lits[k] = falseLit;
                    watches[litNot(lits[1])].push_back(newW);
                    found = true; break;
                }
            }
            if (found) { i++; continue; }
            
            *j++ = newW; i++;
            if (litVal(lits[0]) == 2) {
                while (i < end) *j++ = *i++;
                ws.resize(j-beg);
                return cr;
            }
            assign(lits[0], cr);
        }
        ws.resize(j-beg);
    }
    return CRef_Undef;
}

// ============ Conflict analysis ============
static vector<int> toclear;

static bool litRedundant(int l) {
    static vector<int> stack;
    stack.clear();
    stack.push_back(l);
    int top = toclear.size();
    while (!stack.empty()) {
        int lit = stack.back(); stack.pop_back();
        CRef cr = reasons[litVar(lit)];
        if (cr.ofs < 0) {
            for (int i = top; i < (int)toclear.size(); i++) seen[litVar(toclear[i])] = false;
            toclear.resize(top); return false;
        }
        int* lits = clauseLits(cr);
        int sz = clauseSize(cr);
        for (int i = 1; i < sz; i++) {
            int v = litVar(lits[i]);
            if (!seen[v] && levels[v] > 0) {
                if (reasons[v].ofs >= 0) {
                    seen[v] = true;
                    toclear.push_back(lits[i]);
                    stack.push_back(lits[i]);
                } else {
                    for (int k = top; k < (int)toclear.size(); k++) seen[litVar(toclear[k])] = false;
                    toclear.resize(top); return false;
                }
            }
        }
    }
    return true;
}

static int computeLBD(const vector<int>& cl) {
    lbdCounter++;
    int lbd = 0;
    for (auto l : cl) {
        int lv = levels[litVar(l)];
        if (lbdStamp[lv] != lbdCounter) { lbdStamp[lv] = lbdCounter; lbd++; }
    }
    return lbd;
}

static int analyze(CRef confl, vector<int>& outLearnt) {
    outLearnt.clear();
    outLearnt.push_back(-1);
    int pathC = 0, idx = trailSz - 1, p = -1;
    CRef cr = confl;
    
    do {
        int* lits = clauseLits(cr);
        int sz = clauseSize(cr);
        for (int j = (p<0?0:1); j < sz; j++) {
            int v = litVar(lits[j]);
            if (!seen[v] && levels[v] > 0) {
                seen[v] = true;
                bumpVar(v);
                if (levels[v] >= nLevels) pathC++;
                else outLearnt.push_back(lits[j]);
            }
        }
        while (!seen[litVar(trail[idx])]) idx--;
        p = trail[idx--];
        seen[litVar(p)] = false;
        pathC--;
        if (pathC > 0) cr = reasons[litVar(p)];
    } while (pathC > 0);
    
    outLearnt[0] = litNot(p);
    
    toclear.clear();
    for (auto l : outLearnt) { seen[litVar(l)] = true; toclear.push_back(l); }
    int j = 1;
    for (int i = 1; i < (int)outLearnt.size(); i++)
        if (reasons[litVar(outLearnt[i])].ofs < 0 || !litRedundant(outLearnt[i]))
            outLearnt[j++] = outLearnt[i];
    outLearnt.resize(j);
    for (auto l : toclear) seen[litVar(l)] = false;
    
    if ((int)outLearnt.size() <= 1) return 0;
    int maxLev = 0, maxIdx = 1;
    for (int i = 1; i < (int)outLearnt.size(); i++) {
        if (levels[litVar(outLearnt[i])] > maxLev) {
            maxLev = levels[litVar(outLearnt[i])]; maxIdx = i;
        }
    }
    swap(outLearnt[1], outLearnt[maxIdx]);
    return levels[litVar(outLearnt[1])];
}

// ============ Backtrack ============
static void cancelUntil(int level) {
    if (nLevels <= level) return;
    for (int i = trailSz-1; i >= trailLim[level]; i--) {
        int v = litVar(trail[i]);
        polarity[v] = litSign(trail[i]) ? -1 : 1;
        assigns[v] = 0;
        if (heapIdx[v] < 0) heapInsert(v);
    }
    trailSz = trailLim[level]; nLevels = level; qhead = trailSz;
}

// ============ Clause reduction ============
static void reduceDB() {
    sort(learnts.begin(), learnts.end(), [](CRef a, CRef b) {
        return clauseLBD(a) > clauseLBD(b);
    });
    int removeCnt = learnts.size() / 2;
    int j = 0;
    for (int i = 0; i < (int)learnts.size(); i++) {
        CRef cr = learnts[i];
        bool shouldRemove = (i < removeCnt) && (clauseSize(cr) > 2) && (clauseLBD(cr) > 3);
        if (shouldRemove) {
            int* lits = clauseLits(cr);
            int v = litVar(lits[0]);
            if (assigns[v] != 0 && reasons[v].ofs == cr.ofs) {
                learnts[j++] = cr; continue;
            }
            // Delete from watches
            for (int w = 0; w < 2; w++) {
                auto& ws = watches[litNot(lits[w])];
                for (int k = 0; k < (int)ws.size(); k++) {
                    if (ws[k].cref.ofs == cr.ofs) { ws[k] = ws.back(); ws.pop_back(); break; }
                }
            }
            // Log deletion in proof
            if (proofOn && clauseProofId(cr) > 0) {
                proofDelete(clauseProofId(cr));
            }
        } else {
            learnts[j++] = cr;
        }
    }
    learnts.resize(j);
}

// ============ Parse ============
static bool parseDimacs(const char* fn) {
    FILE* f = fopen(fn, "r");
    if (!f) return false;
    fseek(f, 0, SEEK_END);
    long fsz = ftell(f);
    fseek(f, 0, SEEK_SET);
    char* buf = (char*)malloc(fsz+1);
    fread(buf, 1, fsz, f);
    buf[fsz] = 0;
    fclose(f);
    
    char* p = buf;
    vector<int> lits;
    while (*p) {
        while (*p==' '||*p=='\t'||*p=='\n'||*p=='\r') p++;
        if (!*p) break;
        if (*p=='c') { while (*p&&*p!='\n') p++; continue; }
        if (*p=='p') {
            while (*p&&*p!='f') p++;
            if (*p=='f') {
                p++;
                while (*p==' ') p++;
                nVars=0; while (*p>='0'&&*p<='9') nVars=nVars*10+(*p++-'0');
                while (*p==' ') p++;
                nOrigClauses=0; while (*p>='0'&&*p<='9') nOrigClauses=nOrigClauses*10+(*p++-'0');
            }
            while (*p&&*p!='\n') p++;
            continue;
        }
        int sign=1, val=0;
        if (*p=='-') { sign=-1; p++; }
        if (*p<'0'||*p>'9') { p++; continue; }
        while (*p>='0'&&*p<='9') val=val*10+(*p++-'0');
        val *= sign;
        if (val==0) {
            if (!lits.empty()) {
                int il[lits.size()];
                for (int i=0;i<(int)lits.size();i++) il[i]=fromDimacs(lits[i]);
                origClauses.push_back(allocClause(il, lits.size(), false));
                lits.clear();
            }
        } else lits.push_back(val);
    }
    free(buf);
    return true;
}

// ============ Verify ============
static bool verifyModel() {
    for (auto cr : origClauses) {
        int* lits = clauseLits(cr);
        int sz = clauseSize(cr);
        bool sat = false;
        for (int i = 0; i < sz; i++) { if (litVal(lits[i])==1) { sat=true; break; } }
        if (!sat) return false;
    }
    return true;
}

// ============ Main ============
int main(int argc, char** argv) {
    t0 = wallTime();
    if (argc < 2) return 0;
    
    const char* cnfFile = argv[1];
    const char* proofPath = (argc >= 3) ? argv[2] : nullptr;
    
    caCap = 1<<20; ca = (uint32_t*)malloc(sizeof(uint32_t)*caCap); caSz = 0;
    proofBuf = (char*)malloc(proofBufCap);
    proofBufSz = 0;
    
    if (!parseDimacs(cnfFile)) return 0;
    
    assigns = (uint8_t*)calloc(nVars, 1);
    levels = (int*)malloc(sizeof(int)*nVars);
    reasons = (CRef*)malloc(sizeof(CRef)*nVars);
    for (int i = 0; i < nVars; i++) reasons[i] = CRef_Undef;
    trail = (int*)malloc(sizeof(int)*nVars);
    trailLim = (int*)malloc(sizeof(int)*nVars);
    trailSz = 0; nLevels = 0; qhead = 0;
    
    activity = (double*)calloc(nVars, sizeof(double));
    varInc = 1.0;
    heap = (int*)malloc(sizeof(int)*nVars);
    heapIdx = (int*)malloc(sizeof(int)*nVars);
    heapSz = 0;
    
    polarity = (int8_t*)calloc(nVars, 1);
    // Compute Jeroslow-Wang initial phase
    {
        double* jwPos = (double*)calloc(nVars, sizeof(double));
        double* jwNeg = (double*)calloc(nVars, sizeof(double));
        for (auto cr : origClauses) {
            int* lits = clauseLits(cr);
            int sz = clauseSize(cr);
            double w = 1.0;
            for (int k = 0; k < sz; k++) w *= 0.5; // 2^(-sz)
            for (int k = 0; k < sz; k++) {
                int v = litVar(lits[k]);
                if (litSign(lits[k])) jwNeg[v] += w;
                else jwPos[v] += w;
            }
        }
        for (int i = 0; i < nVars; i++) {
            polarity[i] = (jwPos[i] >= jwNeg[i]) ? 1 : -1;
        }
        free(jwPos); free(jwNeg);
    }
    seen = (bool*)calloc(nVars, 1);
    lbdStamp = (uint32_t*)calloc(nVars+1, sizeof(uint32_t));
    lbdCounter = 0;
    
    watches = new vector<Watcher>[2*nVars];
    
    for (auto cr : origClauses) {
        int* lits = clauseLits(cr);
        int sz = clauseSize(cr);
        double w = 1.0/sz;
        for (int i = 0; i < sz; i++) activity[litVar(lits[i])] += w;
    }
    for (int i = 0; i < nVars; i++) heapIdx[i] = -1;
    for (int i = 0; i < nVars; i++) heapInsert(i);
    
    bool unsat = false;
    for (auto cr : origClauses) {
        int* lits = clauseLits(cr);
        int sz = clauseSize(cr);
        if (sz == 0) { unsat = true; break; }
        if (sz == 1) {
            if (litVal(lits[0]) == 2) { unsat = true; break; }
            if (litVal(lits[0]) == 0) assign(lits[0], CRef_Undef);
        } else {
            watches[litNot(lits[0])].push_back({cr, lits[1]});
            watches[litNot(lits[1])].push_back({cr, lits[0]});
        }
    }
    
    int result = 0;
    if (unsat) {
        result = 20;
    } else {
        if (proofPath) {
            proofOn = true;
            proofFp = fopen(proofPath, "w");
            if (proofFp) {
                char hdr[64];
                int hlen = sprintf(hdr, "pseudo-Boolean proof version 2.0\nf %d\n", (int)origClauses.size());
                fwrite(hdr, 1, hlen, proofFp);
                proofId = origClauses.size();
            }
        }
        
        CRef confl = propagate();
        if (confl.ofs >= 0) {
            result = 20;
        } else {
            int conflicts = 0;
            int nextReduce = 1000;
            double lbdSum = 0; int lbdCnt = 0;
            double recentLbdSum = 0; int recentLbdCnt = 0;
            int lastRestart = 0;
            
            while (true) {
                confl = propagate();
                if (confl.ofs >= 0) {
                    conflicts++;
                    if (nLevels == 0) { result = 20; break; }
                    
                    vector<int> learnt;
                    int btLevel = analyze(confl, learnt);
                    cancelUntil(btLevel);
                    
                    int lbd = computeLBD(learnt);
                    
                    int pid = 0;
                    if (proofOn) pid = proofWriteClause(learnt.data(), learnt.size());
                    
                    if ((int)learnt.size() == 1) {
                        assign(learnt[0], CRef_Undef);
                    } else {
                        CRef cr = allocClause(learnt.data(), learnt.size(), true, lbd, pid);
                        learnts.push_back(cr);
                        int* lits = clauseLits(cr);
                        watches[litNot(lits[0])].push_back({cr, lits[1]});
                        watches[litNot(lits[1])].push_back({cr, lits[0]});
                        assign(learnt[0], cr);
                    }
                    
                    varInc /= varDecay;
                    lbdSum += lbd; lbdCnt++;
                    recentLbdSum += lbd; recentLbdCnt++;
                    
                    if (conflicts - lastRestart >= 20 && recentLbdCnt >= 20) {
                        double gAvg = lbdSum / lbdCnt;
                        double rAvg = recentLbdSum / recentLbdCnt;
                        if (rAvg > 0.8 * gAvg) {
                            cancelUntil(0);
                            lastRestart = conflicts;
                            recentLbdSum = 0; recentLbdCnt = 0;
                        }
                    }
                    
                    if (conflicts >= nextReduce) {
                        reduceDB();
                        nextReduce = conflicts + 1000 + 300*(int)(learnts.size()/5000);
                    }
                    if ((conflicts & 0xFFF) == 0 && timeout()) { result = 0; break; }
                } else {
                    int next = -1;
                    while (heapSz > 0) {
                        int v = heapPop();
                        if (assigns[v] == 0) { next = v; break; }
                    }
                    if (next < 0) { result = 10; break; }
                    trailLim[nLevels++] = trailSz;
                    assign(mkLit(next, polarity[next] < 0), CRef_Undef);
                }
            }
        }
    }
    
    if (result == 10) {
        if (!verifyModel()) {
            fprintf(stderr, "Model verification failed!\n");
            printf("s UNKNOWN\n");
            if (proofFp) fclose(proofFp);
            return 0;
        }
        printf("s SATISFIABLE\nv ");
        for (int i = 0; i < nVars; i++) printf("%d ", (i+1)*(assigns[i]==1?1:-1));
        printf("0\n");
        if (proofFp) fclose(proofFp);
        return 10;
    }
    if (result == 20) {
        printf("s UNSATISFIABLE\n");
        if (proofOn && proofFp) {
            proofId++;
            const char* empty = "u >= 1 ;\n";
            proofWrite(empty, strlen(empty));
            char tail[128];
            int tlen = sprintf(tail, "output NONE\nconclusion UNSAT : %d\nend pseudo-Boolean proof\n", proofId);
            proofWrite(tail, tlen);
            proofFlush();
            fclose(proofFp);
            proofFp = nullptr;
        }
        return 20;
    }
    
    printf("s UNKNOWN\n");
    if (proofFp) { proofFlush(); fclose(proofFp); }
    return 0;
}
