#include using namespace std; // BDD approach: build a reduced ordered BDD for the predicate L <= x <= R // where x is a 20-bit number. // Each BDD node corresponds to a bit position and has two children (0 and 1). // Terminal nodes: TRUE (accept) and FALSE (reject). // We only need paths that reach TRUE, so FALSE branches are simply absent. // Key difference from current approach: BDD sharing can happen across bit levels // if two sub-functions are identical. // Actually, this is EXACTLY what the current solution does with the recursive // range decomposition and memoization. The map,int> rc // is essentially BDD node caching. // But BDD has a crucial additional optimization: if two sub-functions at // DIFFERENT bit positions are identical, they can share a node. // In standard ROBDD, nodes are ordered by variable, so a variable-k node // can only point to variable-(k-1) nodes. No cross-level sharing. // In our problem, the DAG doesn't need to be a BDD. It just needs to be // a DAG where paths spell out binary representations of numbers in [L,R]. // Can we build a "relaxed" BDD where a node at level k can point to // a node at level k-2 (skipping a level)? // If so, the skipped bit is ALWAYS a specific value (constrained path). // But then some paths have 19 edges and others have 20. Wrong! // Unless we ADD dummy "pass-through" nodes to equalize path lengths. // But that would ADD nodes, not save them. // OK here's another idea. What if some paths have FEWER edges but // the first bit is still 1 and the number is valid? // For example, a number like 577837 is 20 bits. But what about the // number 999141? Also 20 bits. // ALL numbers in [577837, 979141] are 20 bits. So all paths must be 20 edges. // I think we're stuck at 55. Let me try one more thing: a SAT-based search // for tiny cases to see if cross-depth sharing ever helps. int L, R; struct Edge { int to, w; }; vector> adj; int n_nodes; vector indeg, outdeg; bool verify(int L, int R) { int start = -1, end_node = -1; for (int i = 1; i <= n_nodes; i++) { if (indeg[i] == 0) start = i; if (outdeg[i] == 0) end_node = i; } if (start < 0 || end_node < 0) return false; // Check no leading zeros for (auto& e : adj[start]) if (e.w == 0) return false; int cnt = 0; vector vis(R + 1, 0); bool error = false; function dfs = [&](int x, int val) { if (error) return; if (outdeg[x] == 0) { cnt++; if (cnt > 1000000 || val < L || val > R) { error = true; return; } vis[val]++; if (vis[val] > 1) { error = true; return; } return; } for (auto& e : adj[x]) dfs(e.to, val * 2 + e.w); }; dfs(start, 0); if (error) return false; for (int i = L; i <= R; i++) if (vis[i] == 0) return false; return true; } // For small ranges, try to find if the ADFA can be beaten void solve_optimal(int L, int R) { // Build ADFA map, int> state_id; int next_id = 0; vector> trans; function get_state = [&](int k, int lo, int hi) -> int { if (lo > hi) return -1; auto key = make_tuple(k, lo, hi); auto it = state_id.find(key); if (it != state_id.end()) return it->second; int id = next_id++; state_id[key] = id; trans.push_back({-1, -1}); if (k == 0) return id; int mid = 1 << (k - 1); trans[id][0] = get_state(k - 1, lo, min(hi, mid - 1)); trans[id][1] = get_state(k - 1, max(lo, mid) - mid, hi - mid); return id; }; int bits = 0; int tmp = R; while (tmp > 0) { bits++; tmp >>= 1; } int base = 1 << (bits - 1); get_state(bits - 1, max(0, L - base), min(base - 1, R - base)); int adfa_nodes = next_id + 1; // +1 for START // Now try to build with fewer nodes using brute force // (only feasible for tiny ranges) // Skip this for large ranges cout << "L=" << L << " R=" << R << " bits=" << bits << " ADFA=" << adfa_nodes << endl; } int main() { // Test on small cases to see if ADFA is always optimal cout << "Testing small cases..." << endl; for (int l = 1; l <= 30; l++) { for (int r = l; r <= 30; r++) { int bits = 0; int tmp = r; while (tmp > 0) { bits++; tmp >>= 1; } int bitsL = 0; tmp = l; while (tmp > 0) { bitsL++; tmp >>= 1; } // Only test same-bit-length ranges if (bitsL == bits) { solve_optimal(l, r); } } } // Now for the actual problem cout << "\nActual problem:" << endl; solve_optimal(577837, 979141); return 0; }