如何在C++中基于约束满足实现算术密码问题的变量域与约束生成
实现算术密码的约束满足解法(以TWO + TWO = FOUR为例)
我来帮你搞定这个算术密码的C++实现问题!先从核心的约束推导说起,再一步步拆解代码里的域生成和进位处理——毕竟你已经理解了推导逻辑,重点就是怎么把这些思路转成可运行的代码对吧?
首先先明确问题:
算术密码问题示例:
T W O + T W O = F O U R,需要为每个字母分配0-9的唯一数字,满足等式。比如可行解是7 6 5 + 7 6 5 = 1 5 3 0
暴力法虽然能跑,但效率太低,咱们就用你说的约束满足思路来实现,核心就是通过推导不断缩小每个字母的可选数字范围(也就是域),再结合回溯搜索找到解。
核心约束推导(先把变量域缩到最小)
咱们先把推导出来的约束一条条理清楚,这是降低复杂度的关键:
- 个位约束:
O + O = R(或O + O = 10 + R,带进位),所以R一定是偶数,直接把O的域缩小为{0,2,4,6,8} - 最高位约束:两个三位数相加得到四位数,最高位F只能是1(最大的两个三位数相加999+999=1998,进位最多是1),所以
F = 1,直接确定这个变量的值 - 百位约束:千位是F=1,说明百位相加产生了进位1,因此
T + T >= 9(哪怕十位没有进位,T+T也要≥10才能进位到千位),所以T的域缩小为{5,6,7,8,9} - 十位约束:
W + W + 个位进位 = U + 10 * 百位进位,这里的进位只能是0或1(两个个位数相加最多8+8=16,加个位进位1也才17),结合前面的O、T域,还能进一步缩小W和U的范围
C++实现关键步骤(域生成与进位处理)
接下来把这些推导转成代码,重点解决你困惑的域生成和进位处理:
1. 定义数据结构
首先用哈希表存储每个字母的可选域,再用一个哈希表跟踪已使用的数字(避免重复),还要记录进位变量:
#include <unordered_map> #include <vector> #include <string> #include <algorithm> #include <iostream> // 存储每个字母的可选数字域 std::unordered_map<char, std::vector<int>> domains; // 记录已使用的数字,保证每个字母对应唯一数字 std::unordered_map<int, bool> used; // 进位变量:carry1=个位→十位,carry2=十位→百位,carry3=百位→千位 int carry1 = 0, carry2 = 0, carry3 = 0;
2. 初始化初始约束域
根据咱们的推导,先给每个变量设置初始的可选范围:
void initDomains() { // 直接确定F的值 domains['F'] = {1}; used[1] = true; // F用了1,其他字母不能再用 // 缩小O和T的初始域 domains['O'] = {0, 2, 4, 6, 8}; domains['T'] = {5, 6, 7, 8, 9}; // 其他变量初始为0-9,排除已被F占用的1 std::vector<int> default_domain; for (int i = 0; i <= 9; ++i) { if (i != 1) default_domain.push_back(i); } domains['W'] = default_domain; domains['R'] = default_domain; domains['U'] = default_domain; }
3. 约束传播函数(不断缩小域)
写一个函数来迭代应用约束,直到没有域再能缩小为止——这是约束满足算法的核心:
bool propagateConstraints() { bool updated = false; // 从O的域推导R的可能值:R = 2*O 或 2*O-10(当O+O≥10时,carry1=1) std::vector<int> new_R_domain; for (int o : domains['O']) { int r = 2 * o; int temp_carry1 = 0; if (r >= 10) { r -= 10; temp_carry1 = 1; } // R不能是已使用的数字,且要唯一 if (!used[r] && r != 1) { new_R_domain.push_back(r); carry1 = temp_carry1; // 更新个位进位 } } // 去重并更新域 std::sort(new_R_domain.begin(), new_R_domain.end()); auto last = std::unique(new_R_domain.begin(), new_R_domain.end()); new_R_domain.erase(last, new_R_domain.end()); if (new_R_domain != domains['R']) { domains['R'] = new_R_domain; updated = true; } // 从T和进位约束推导O的可能值:T*2 + carry2 = O + 10*carry3,而carry3=1(因为F=1) std::vector<int> new_O_domain; for (int t : domains['T']) { for (int o : domains['O']) { // 十位进位carry2只能是0或1 for (int c2 : {0, 1}) { if (2 * t + c2 == o + 10) { new_O_domain.push_back(o); carry2 = c2; // 更新十位进位 } } } } std::sort(new_O_domain.begin(), new_O_domain.end()); last = std::unique(new_O_domain.begin(), new_O_domain.end()); new_O_domain.erase(last, new_O_domain.end()); if (new_O_domain != domains['O']) { domains['O'] = new_O_domain; updated = true; } // 推导W和U的域:W*2 + carry1 = U + 10*carry2 std::vector<int> new_W_domain, new_U_domain; for (int w : domains['W']) { for (int u : domains['U']) { if (2 * w + carry1 == u + 10 * carry2) { if (!used[w] && w != 1) new_W_domain.push_back(w); if (!used[u] && u != 1) new_U_domain.push_back(u); } } } // 去重并更新 std::sort(new_W_domain.begin(), new_W_domain.end()); last = std::unique(new_W_domain.begin(), new_W_domain.end()); new_W_domain.erase(last, new_W_domain.end()); if (new_W_domain != domains['W']) { domains['W'] = new_W_domain; updated = true; } std::sort(new_U_domain.begin(), new_U_domain.end()); last = std::unique(new_U_domain.begin(), new_U_domain.end()); new_U_domain.erase(last, new_U_domain.end()); if (new_U_domain != domains['U']) { domains['U'] = new_U_domain; updated = true; } return updated; }
4. 回溯搜索(结合约束域找解)
当域缩小到一定程度后,用回溯法尝试每个变量的可能值,同时检查约束:
bool backtrack(std::vector<char>& variables, int idx) { // 所有变量都已赋值,验证等式是否成立 if (idx == variables.size()) { int TWO = domains['T'][0] * 100 + domains['W'][0] * 10 + domains['O'][0]; int FOUR = domains['F'][0] * 1000 + domains['O'][0] * 100 + domains['U'][0] * 10 + domains['R'][0]; if (TWO + TWO == FOUR) { std::cout << "找到可行解:" << std::endl; std::cout << domains['T'][0] << " " << domains['W'][0] << " " << domains['O'][0] << " + " << domains['T'][0] << " " << domains['W'][0] << " " << domains['O'][0] << " = " << domains['F'][0] << " " << domains['O'][0] << " " << domains['U'][0] << " " << domains['R'][0] << std::endl; return true; } return false; } char var = variables[idx]; // 遍历当前变量的所有可选值 for (int num : domains[var]) { if (!used[num]) { used[num] = true; // 临时保存当前域,赋值为当前数字 std::vector<int> temp_domain = domains[var]; domains[var] = {num}; // 传播约束,缩小其他变量的域 while (propagateConstraints()); // 继续递归处理下一个变量 if (backtrack(variables, idx + 1)) { return true; } // 回溯,恢复之前的域和已使用状态 domains[var] = temp_domain; used[num] = false; } } return false; }
5. 主函数调用
int main() { initDomains(); // 优先处理域小的变量(F、O、T),减少回溯次数 std::vector<char> variables = {'F', 'O', 'T', 'R', 'W', 'U'}; // 先执行一次约束传播,缩小初始域 while (propagateConstraints()); // 启动回溯搜索 if (!backtrack(variables, 0)) { std::cout << "未找到可行解" << std::endl; } return 0; }
几个关键注意点
- 变量处理顺序:优先处理域小的变量(比如已经确定的F,然后是O、T),可以大幅减少回溯的次数,提升效率
- 约束传播迭代:每次赋值后都要反复执行约束传播,直到没有域再能缩小,这样能提前排除很多无效的可能性
- 唯一性检查:一定要用
used哈希表跟踪已使用的数字,保证每个字母对应唯一的数字
内容的提问来源于stack exchange,提问作者SleepyMusk




