diff --git a/algorithmic/problems/145/checker.cpp b/algorithmic/problems/145/checker.cpp index 0843fbb48..a5278762b 100644 --- a/algorithmic/problems/145/checker.cpp +++ b/algorithmic/problems/145/checker.cpp @@ -1,6 +1,8 @@ // checker.cpp -// C++11 testlib special judge for the Slitherlink-like problem. -// Fully reproduces behavior of the original Python+Z3 checker. +// testlib special judge for the Number Loop construction problem. +// Counts the single loops satisfying every clue of the contestant's puzzle, +// capped at six, with an exact plug DP over the grid points: the count does +// not depend on how weakly the clues constrain the board. // Requires testlib.h in include path. #include "testlib.h" @@ -58,8 +60,6 @@ double score_percent(int solution_count){ return 50.0 + 50.0 * u; } -// helper printing to stderr (like original) -void eprint(const string &s){ cerr << s << "\n"; } void myAssert(bool cond, const string &msg){ if(!cond) { // In SPJ we usually call quitf for WA, but here follow original: print to stderr and exit @@ -93,405 +93,180 @@ vector readSol(InStream &in){ return res; } -// Edge representation -struct Edge { - bool is_h; // horizontal if true, vertical if false - int i, j; // coordinates: H[i][j] or V[i][j] - vector> adj_cells; // adjacent cells (r,c) - vector> adj_points; // endpoints (pi,pj) - int score; // heuristic: number of adjacent numbered cells -}; - -vector edges; -int Ecnt; - -// Backtracking state -vector edge_val; // -1 unassigned, 0 false, 1 true -int cell_assigned_true[N][N]; -int cell_unassigned[N][N]; -int point_deg[N+1][N+1]; -int point_unassigned[N+1][N+1]; - -struct Change { - int type; // 0: edge val, 1: cell_assigned_true, 2: cell_unassigned, 3: point_deg, 4: point_unassigned - int a; // encoded index (edge idx or r*100 + c or pi*100 + pj) - int old; -}; -vector changes; - -void apply_assign(int eidx, int val){ - // assume edge_val[eidx] == -1 - changes.push_back({0, eidx, edge_val[eidx]}); - edge_val[eidx] = val; - const Edge &ed = edges[eidx]; - // adjacent cells - for(size_t k=0;k j : V[i-1][k-1], entering (i, k-1) from above +// A slot is 0 (no edge) or an end of an open path, 1 for its left end and 2 +// for its right end, so the slots form a balanced bracket sequence. Cell +// (r, c) is checked when point (r+1, c) decides its bottom edge; its top edge +// H[r][c] has left the profile by then, so it is kept as one bit per column. +// One more bit records that the loop is closed, after which no edge may be +// added. Every edge set is reached by exactly one path through the DP. + +const int P = N + 1; // grid points per side +const int SLOTS = P + 1; +const int TOP_SHIFT = 2 * SLOTS; // top-edge bits follow the slots +const uint64_t PLUG_MASK = (1ULL << TOP_SHIFT) - 1; +const uint64_t CLOSED = 1ULL << (TOP_SHIFT + N); + +int slot_of(uint64_t s, int k){ return (int)((s >> (2 * k)) & 3); } + +uint64_t with_slot(uint64_t s, int k, int v){ + return (s & ~(3ULL << (2 * k))) | ((uint64_t)v << (2 * k)); } -void rollback_to(int sz){ - while((int)changes.size() > sz){ - Change ch = changes.back(); changes.pop_back(); - if(ch.type == 0){ - edge_val[ch.a] = ch.old; - } else if(ch.type == 1){ - int r = ch.a / 100, c = ch.a % 100; - cell_assigned_true[r][c] = ch.old; - } else if(ch.type == 2){ - int r = ch.a / 100, c = ch.a % 100; - cell_unassigned[r][c] = ch.old; - } else if(ch.type == 3){ - int pi = ch.a / 100, pj = ch.a % 100; - point_deg[pi][pj] = ch.old; - } else if(ch.type == 4){ - int pi = ch.a / 100, pj = ch.a % 100; - point_unassigned[pi][pj] = ch.old; - } +// Slot holding the other end of the path that ends in slot k. +int matching_slot(uint64_t s, int k){ + const int own = slot_of(s, k); + const int dir = own == 1 ? 1 : -1; + int depth = 0; + for(int t = k; t >= 0 && t < SLOTS; t += dir){ + const int v = slot_of(s, t); + if(v == 0) continue; + depth += v == own ? 1 : -1; + if(depth == 0) return t; } -} - -// Propagation: enforce cell counts and point degree constraints (0 or 2) -bool contradiction_check_and_propagate(){ - bool changed = true; - int iter = 0; - while(changed){ - changed = false; - iter++; - if(iter > 300000) break; // safety - // cells - for(int r=0;r need) return false; - if(have + rem < need) return false; - if(rem == 0){ - if(have != need) return false; - } else { - if(have == need){ - // force remaining adjacent edges false - for(int e=0;e all remaining edges adjacent must be false - for(int e=0;e best_score){ - best_score = edges[e].score; - best = e; - } - } - if(best != -1) return best; - for(int e=0;e> &H, vector> &V){ - H.assign(N+1, vector(N,0)); - V.assign(N, vector(N+1,0)); - for(int e=0;e= N || c < 0 || c >= N || grid[r][c] == ' ') return -1; + return grid[r][c] - '0'; } -// split assignment into loops (each loop is vector of (tag,i,j)) -vector > > split_loops_from_assignment(){ - vector > H(N+1, vector(N,0)); - vector > V(N, vector(N+1,0)); - for(int e=0;e table; + vector used; // occupied entries + + StateMap(){ bits = 0; rehash(16); } + + size_t find(uint64_t state) const { + const size_t mask = table.size() - 1; + size_t h = (size_t)((state * 0x9E3779B97F4A7C15ULL) >> (64 - bits)); + while(table[h] != EMPTY && (table[h] & STATE_BITS) != state) h = (h + 1) & mask; + return h; } - auto incident_edges_at_point = [&](int pi, int pj){ - vector< tuple > res; - if(pi>0 && V[pi-1][pj]) res.push_back(make_tuple('v', pi-1, pj)); - if(pi < N && V[pi][pj]) res.push_back(make_tuple('v', pi, pj)); - if(pj>0 && H[pi][pj-1]) res.push_back(make_tuple('h', pi, pj-1)); - if(pj < N && H[pi][pj]) res.push_back(make_tuple('h', pi, pj)); - return res; - }; - - auto remove_edge_mat = [&](char tag, int i, int j){ - if(tag=='h') H[i][j] = 0; - else V[i][j] = 0; - }; - - vector > > loop_list; - - while(true){ - bool found_edge = false; - char start_tag = 0; - int start_i=-1, start_j=-1; - for(int i=0;i<=N && !found_edge;i++){ - for(int j=0;j old_table(1ULL << new_bits, EMPTY); + vector old_used; + table.swap(old_table); + used.swap(old_used); + bits = new_bits; + for(uint32_t h : old_used){ + const size_t g = find(state(old_table[h])); + table[g] = old_table[h]; + used.push_back((uint32_t)g); } - for(int i=0;i > loop; - char prev_tag = 0; int prev_i = -1, prev_j = -1; - int init_pi = cur_pi, init_pj = cur_pj; - while(true){ - vector< tuple > inc = incident_edges_at_point(cur_pi, cur_pj); - bool moved = false; - for(size_t k=0;k(inc[k]); - int ei = get<1>(inc[k]); - int ej = get<2>(inc[k]); - if(prev_tag != 0 && t==prev_tag && ei==prev_i && ej==prev_j) continue; - loop.push_back(inc[k]); - remove_edge_mat(t, ei, ej); - // move to other endpoint - if(t=='h'){ - if(cur_pi==ei && cur_pj==ej){ - prev_tag = 'h'; prev_i = ei; prev_j = ej; - cur_pi = ei; cur_pj = ej+1; - } else { - prev_tag = 'h'; prev_i = ei; prev_j = ej; - cur_pi = ei; cur_pj = ej; - } - } else { - if(cur_pi==ei && cur_pj==ej){ - prev_tag = 'v'; prev_i = ei; prev_j = ej; - cur_pi = ei+1; cur_pj = ej; - } else { - prev_tag = 'v'; prev_i = ei; prev_j = ej; - cur_pi = ei; cur_pj = ej; - } - } - moved = true; - break; - } - if(!moved) break; - if(cur_pi==init_pi && cur_pj==init_pj && !loop.empty()) break; - } - if(!loop.empty()) loop_list.push_back(loop); - else break; } - return loop_list; -} -// generate hv/vv from one loop and check against grid -bool generateSol_from_loop(const vector< tuple > &loop, vector> &Hout, vector> &Vout){ - Hout.assign(N+1, vector(N,0)); - Vout.assign(N, vector(N+1,0)); - vector> cnt(N, vector(N,0)); - for(size_t k=0;k(loop[k]); - int i = get<1>(loop[k]); - int j = get<2>(loop[k]); - if(tag=='h'){ - Hout[i][j] = 1; - if(i < N) cnt[i][j] += 1; - if(i > 0) cnt[i-1][j] += 1; - } else { - Vout[i][j] = 1; - if(j < N) cnt[i][j] += 1; - if(j > 0) cnt[i][j-1] += 1; - } + static uint64_t state(uint64_t entry){ return entry & STATE_BITS; } + static int ways(uint64_t entry){ return (int)(entry >> WAYS_SHIFT); } + + // ways[s] += w, capped at limit (below 256, to fit in the entry). + void add(uint64_t s, int w, int limit){ + if(2 * (used.size() + 1) > table.size()) rehash(bits + 1); + const size_t h = find(s); + int now = w; + if(table[h] == EMPTY) used.push_back((uint32_t)h); + else now += ways(table[h]); + table[h] = s | ((uint64_t)min(limit, now) << WAYS_SHIFT); } - for(int i=0;i> &H, const vector> &V){ - string s; - s.reserve((N+1)*N + N*(N+1) + 10); - for(int i=0;i<=N;i++){ - for(int j=0;j> &H, const vector> &V){ - eprint("Sol:"); - for(int i=0;i<=N;i++){ - string s = " "; - for(int k=0;k= 0){ + const int have = (int)((s >> (TOP_SHIFT + j)) & 1) + (U != 0) + + (slot_of(s, j + 2) != 0) + right; + if(have != d_done) continue; + } + if(d_left >= 0){ + const int have = (L != 0) + (slot_of(s, j - 1) != 0) + down; + if(have > d_left || have + 1 < d_left) continue; + } + if(d_here >= 0 && (right + down > d_here || right + down + 2 < d_here)) continue; + + uint64_t t = with_slot(with_slot(s, j, 0), j + 1, 0); + if(L != 0 && U != 0){ + if(L == 1 && U == 1){ + t = with_slot(t, matching_slot(s, j + 1), 1); + } else if(L == 2 && U == 2){ + t = with_slot(t, matching_slot(s, j), 2); + } else if(L == 1 && U == 2){ + // Joining the two ends of one path closes a + // loop, which must then be the only one. + if(t & PLUG_MASK) continue; + t |= CLOSED; + } + } else if(L != 0 || U != 0){ + t = with_slot(t, down ? j : j + 1, L | U); + } else if(deg != 0){ + t = with_slot(with_slot(t, j, 1), j + 1, 2); + } -// container for unique solutions -vector>, vector>>> sol_list; -unordered_set sol_set; // serialized strings for dedup - -// recursive search (stop when found >= limit_solutions) -bool try_search(int limit_solutions){ - int unassigned = 0; - for(int e=0;e > > loops = split_loops_from_assignment(); - for(size_t i=0;i> Hsol, Vsol; - if(generateSol_from_loop(loops[i], Hsol, Vsol)){ - string key = serialize_solution(Hsol, Vsol); - if(sol_set.find(key) == sol_set.end()){ - sol_set.insert(key); - sol_list.push_back(make_pair(Hsol, Vsol)); - if((int)sol_list.size() >= limit_solutions) return true; + if(j < N){ + t &= ~(1ULL << (TOP_SHIFT + j)); + if(right && d_here >= 0) t |= 1ULL << (TOP_SHIFT + j); + } + nxt.add(t, ways, limit); + } } } + swap(cur, nxt); } - return false; - } - int e = pick_next_edge(); - if(e == -1) return false; - for(int val = 0; val <= 1; ++val){ - int save_sz = (int)changes.size(); - apply_assign(e, val); - bool ok = contradiction_check_and_propagate(); - if(ok){ - if(try_search(limit_solutions)) return true; + // Next row: every slot moves one to the right and slot 0 opens empty. + nxt.clear(); + for(uint32_t h : cur.used){ + const uint64_t s = StateMap::state(cur.table[h]); + nxt.add((s & ~PLUG_MASK) | ((s << 2) & PLUG_MASK), StateMap::ways(cur.table[h]), limit); } - rollback_to(save_sz); + swap(cur, nxt); } - return false; + return cur.get(CLOSED); } int main(int argc, char **argv){ @@ -505,80 +280,14 @@ int main(int argc, char **argv){ // read contestant output (ouf) as grid grid grid = readSol(ouf); - // build edge list - edges.clear(); - // horizontals H[0..12][0..11] - for(int i=0;i<=N;i++){ - for(int j=0;j0) ed.adj_cells.push_back(make_pair(i-1,j)); - if(i0) ed.adj_cells.push_back(make_pair(i,j-1)); - if(j