You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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

火山引擎 最新活动