#include using namespace std; // Key idea: at each depth, we have tight_L, tight_R, and free states. // tight_L and tight_R transition to different children. // What if we MERGE tight_L and tight_R into a single node at some depth? // The merged node would have edges from BOTH tight_L and tight_R. // This creates extra paths (tight_L prefix + tight_R edge, and vice versa). // These extra paths might represent numbers outside [L,R] -> BAD. // BUT: if the extra paths DON'T reach END (because they lead to dead ends), // then the merge is safe! // When would extra paths NOT reach END? // If tight_L's bit-0 edge leads to a state that eventually reaches END, // and tight_R's bit-0 edge leads to a different state that also reaches END, // then the merged node's bit-0 edge would go to... both? No, we'd need // to pick one or combine them. // Actually, let me think about this differently. // Instead of merging within a depth, what about a completely different // DAG structure? // APPROACH: Difference encoding // Instead of tracking tight_L and tight_R separately, can we track // the DIFFERENCE between the current prefix and L (or R)? // APPROACH: Shared suffix tree // Build from the END backwards. At depth 19 (1 bit remaining): // State [0,1] accepts both 0 and 1 -> "full" state F1 // State [1,1] accepts only 1 -> "high" state H1 // At depth 18 (2 bits remaining): // State [0,3] accepts all -> full state F2 // State [0,1] accepts 00, 01 -> needs F1 on 0, nothing on 1 // State [1,3] accepts 01, 10, 11 -> needs H1 on 0... wait [1,3]: // bit 0: [1, min(3,1)] = [1,1] -> H1 // bit 1: [max(1,2)-2, 3-2] = [0,1] -> F1 // So [1,3] -> 0:H1, 1:F1 // Hmm, this is getting complex. Let me try a brute-force optimization approach. // Start with the 55-node ADFA, then try random merges and verify. int L = 577837, R = 979141; struct Edge { int to, w; }; bool verify(int n, vector>& adj) { vector indeg(n + 1, 0), outdeg(n + 1, 0); for (int i = 1; i <= n; i++) { outdeg[i] = adj[i].size(); for (auto& e : adj[i]) indeg[e.to]++; } int start = -1, end_node = -1; int cnt1 = 0, cnt2 = 0; for (int i = 1; i <= n; i++) { if (indeg[i] == 0) { cnt1++; start = i; } if (outdeg[i] == 0) { cnt2++; end_node = i; } } if (cnt1 != 1 || cnt2 != 1) return false; for (auto& e : adj[start]) if (e.w == 0) return false; // Check it's a DAG (topological sort) vector topo; vector deg(n + 1, 0); for (int i = 1; i <= n; i++) deg[i] = indeg[i]; queue q; for (int i = 1; i <= n; i++) if (deg[i] == 0) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); topo.push_back(u); for (auto& e : adj[u]) if (--deg[e.to] == 0) q.push(e.to); } if ((int)topo.size() != n) return false; // has cycle int cnt = 0; vector vis(R + 1, 0); bool error = false; function dfs = [&](int x, long long val) { if (error) return; if (val > R) { error = true; return; } // prune if (adj[x].empty()) { cnt++; if (cnt > 500000 || val < L || val > R) { error = true; return; } vis[(int)val]++; if (vis[(int)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; } int main() { // Build the 55-node ADFA map, int> state_id; int next_id = 0; vector> trans; vector> info; 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}); info.push_back(key); 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 base = 1 << 19; int root_child = get_state(19, max(0, L - base), min(base - 1, R - base)); int ns = next_id; // number of ADFA states (not counting START) // Convert to adjacency list with 1-indexed nodes // Node 1 = START, Node 2..ns+1 = ADFA states (mapped from 0..ns-1) int total_n = ns + 1; vector> adj(total_n + 1); // Map: ADFA state id -> node number (2-indexed) auto node_of = [](int state_id) { return state_id + 2; }; // START (node 1) -> root_child (weight 1) adj[1].push_back({node_of(root_child), 1}); for (int i = 0; i < ns; i++) { auto [k, lo, hi] = info[i]; if (k == 0) continue; // END node has no edges if (trans[i][0] != -1) adj[node_of(i)].push_back({node_of(trans[i][0]), 0}); if (trans[i][1] != -1) adj[node_of(i)].push_back({node_of(trans[i][1]), 1}); } cout << "Original: " << total_n << " nodes" << endl; cout << "Verifying... " << flush; if (verify(total_n, adj)) { cout << "OK!" << endl; } else { cout << "FAIL!" << endl; return 1; } // Now try merging pairs of nodes // For each pair (i, j) where i < j, try replacing all references to j with i // and merging j's edges into i. // This is O(n^2) which is fine for n=55. cout << "\nTrying all pairwise merges..." << endl; int found = 0; for (int ni = 1; ni <= total_n; ni++) { for (int nj = ni + 1; nj <= total_n; nj++) { // Try merging nj into ni vector> new_adj(total_n + 1); // we'll compact later for (int u = 1; u <= total_n; u++) { if (u == nj) continue; // skip merged node int mapped_u = u; for (auto& e : adj[u]) { int mapped_to = (e.to == nj) ? ni : e.to; new_adj[mapped_u].push_back({mapped_to, e.w}); } } // Add nj's edges to ni for (auto& e : adj[nj]) { int mapped_to = (e.to == nj) ? ni : e.to; // Check if this edge already exists in ni bool dup = false; for (auto& ee : new_adj[ni]) { if (ee.to == mapped_to && ee.w == e.w) { dup = true; break; } } if (!dup) new_adj[ni].push_back({mapped_to, e.w}); } // Compact: remove node nj, renumber int new_n = total_n - 1; vector> compact(new_n + 1); auto remap = [&](int x) -> int { if (x < nj) return x; if (x == nj) return ni; // shouldn't happen after above return x - 1; }; for (int u = 1; u <= total_n; u++) { if (u == nj) continue; int nu = remap(u); for (auto& e : new_adj[u]) { compact[nu].push_back({remap(e.to), e.w}); } } if (verify(new_n, compact)) { auto [k1, lo1, hi1] = (ni == 1) ? make_tuple(-1, -1, -1) : info[ni - 2]; auto [k2, lo2, hi2] = (nj == 1) ? make_tuple(-1, -1, -1) : info[nj - 2]; cout << "SUCCESS! Merge node " << ni; if (ni > 1) cout << " (k=" << k1 << " [" << lo1 << "," << hi1 << "])"; else cout << " (START)"; cout << " with node " << nj; if (nj > 1) cout << " (k=" << k2 << " [" << lo2 << "," << hi2 << "])"; else cout << " (START)"; cout << " -> " << new_n << " nodes" << endl; found++; } } if (ni % 10 == 0) cout << " Tried merges with node " << ni << "..." << endl; } if (found == 0) { cout << "No valid pairwise merges found. 55 nodes is optimal." << endl; } else { cout << "Found " << found << " valid merges!" << endl; } return 0; }