You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化基于动态规划的运算符括号化求解算法效率?

运算表示例:1⊕2 = 2

动态规划算法优化问题

我正在解决一个动态规划问题:给定一张运算符表,以及一个无括号的运算序列(例如2⊕2⊕2⊕2⊕1⊕3),需要判断是否可通过添加括号改变运算顺序,使序列运算结果等于预期值(如1),同时需找到最左侧的合法括号化方式(示例:((((2⊕2)⊕2)⊕(2⊕1))⊕3) = 1)。

我已实现一种DP解法,通过构建DP表计算所有子序列的可能结果及对应表达式,但该方法会枚举所有解,处理较大输入时效率极低。请问如何优化该算法以提升运行速度?以下是相关代码:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 存储运算结果及对应表达式的结构体
struct Result {
    int value;
    string expression;
};

int main() {
    // 优化cin/cout性能
    ios::sync_with_stdio(0);
    cin.tie(0); 

    int n, m;
    cin >> n >> m; // 读取运算表维度n和序列长度m

    // 读取运算表
    vector<vector<int>> table(n, vector<int>(n));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cin >> table[i][j];
        }
    }

    // 读取运算序列
    vector<int> sequence(m);
    for (int i = 0; i < m; ++i) {
        cin >> sequence[i];
    }

    // 读取预期结果
    int expectedResult;
    cin >> expectedResult;

    // 初始化DP表
    vector<vector<vector<Result>>> dp(m, vector<vector<Result>>(m));

    // 填充基础情况(长度为1的子序列)
    for (int i = 0; i < m; ++i) {
        dp[i][i].push_back({sequence[i], to_string(sequence[i])});
    }

    // 填充长度大于1的子序列
    for (int len = 2; len <= m; ++len) { // 子序列长度(最小为2)
        for (int i = 0; i <= m - len; ++i) { // 子序列起始位置
            int j = i + len - 1; // 子序列结束位置
            for (int k = i; k < j; ++k) { // 分割点,将子序列分为[i,k]和[k+1,j]
                for (const auto &left : dp[i][k]) {
                    for (const auto &right : dp[k + 1][j]) {
                        int result = table[left.value - 1][right.value - 1];
                        string expr = "(" + left.expression + " " + right.expression + ")"; 
                        dp[i][j].push_back({result, expr});
                    }
                }
            }
        }
    }

    // 筛选出结果等于预期值的表达式(取最后找到的一个)
    string lastValidExpression = "";
    for (const auto &res : dp[0][m - 1]) {
        if (res.value == expectedResult) {
            lastValidExpression = res.expression; // 更新为最新找到的表达式
        }
    }

    // 输出结果
    if (!lastValidExpression.empty()) {
        cout << "1\n" << lastValidExpression << endl;
    } else {
        cout << "0" << endl;
    }

    return 0;
}

输入输出示例

示例1

输入:

2 4
1 2
2 1
1 2 2 1
1

输出:

1 
(((1 2) 2) 1)

示例2

输入:

3 6
3 2 1
3 2 1
1 3 3
2 2 2 2 1 3
1

输出:

1
((((2 2) 2) (2 1)) 3)

优化方案

当前实现的最大问题是存储了同一子序列的所有可能结果及表达式,导致空间和时间复杂度爆炸(时间复杂度为O(m³*K²),K是每个子序列的可能结果数)。以下是针对性优化:

1. 去重存储:同一值只保留最左侧的表达式

对于每个子区间[i,j],不需要存储所有能得到某个值的表达式,只需要保留首次得到该值的表达式(因为我们要找最左侧的合法括号化方式,优先左侧分割点的解)。这样可以大幅减少DP表中的元素数量。

修改方式:将dp[i][j]从vector<Result>改为unordered_map<int, string>,键是运算结果值,值是对应的最左侧表达式。当计算出一个值时,如果该值不在map中,才存入对应的表达式;如果已存在,直接跳过(因为首次存入的就是最左侧的解)。

2. 提前终止:一旦找到目标结果可提前退出

在填充DP表的过程中,如果当前处理的区间是[0, m-1]且已经找到预期结果,可以提前终止计算,无需继续枚举所有可能。

3. 避免不必要的字符串拼接(可选)

字符串拼接是耗时操作,我们可以只存储括号化的结构信息(比如分割点),最后再根据结构生成表达式,而不是在DP过程中实时拼接字符串。不过这个优化对代码改动较大,优先考虑前两个优化。

优化后的代码示例

#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); 

    int n, m;
    cin >> n >> m;

    vector<vector<int>> table(n, vector<int>(n));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cin >> table[i][j];
        }
    }

    vector<int> sequence(m);
    for (int i = 0; i < m; ++i) {
        cin >> sequence[i];
    }

    int expectedResult;
    cin >> expectedResult;

    // 优化:用unordered_map存储每个区间的{结果值: 最左侧表达式}
    vector<vector<unordered_map<int, string>>> dp(m, vector<unordered_map<int, string>>(m));

    // 基础情况
    for (int i = 0; i < m; ++i) {
        dp[i][i][sequence[i]] = to_string(sequence[i]);
    }

    bool found = false;
    for (int len = 2; len <= m; ++len) {
        if (found) break; // 找到目标结果提前终止
        for (int i = 0; i <= m - len; ++i) {
            int j = i + len - 1;
            // 优先遍历左侧分割点,保证首次存入的是最左侧表达式
            for (int k = i; k < j; ++k) {
                for (const auto &left_pair : dp[i][k]) {
                    int left_val = left_pair.first;
                    const string &left_expr = left_pair.second;
                    for (const auto &right_pair : dp[k+1][j]) {
                        int right_val = right_pair.first;
                        const string &right_expr = right_pair.second;
                        int res_val = table[left_val - 1][right_val - 1];
                        // 只有当该值未被记录时才存入,保证最左侧的表达式
                        if (dp[i][j].find(res_val) == dp[i][j].end()) {
                            dp[i][j][res_val] = "(" + left_expr + " " + right_expr + ")";
                            // 如果是整个序列且找到预期结果,标记提前终止
                            if (i == 0 && j == m-1 && res_val == expectedResult) {
                                found = true;
                                goto end_loop; // 跳出所有循环
                            }
                        }
                    }
                }
            }
        }
    }
end_loop:

    auto &final_map = dp[0][m-1];
    if (final_map.find(expectedResult) != final_map.end()) {
        cout << "1\n" << final_map[expectedResult] << endl;
    } else {
        cout << "0" << endl;
    }

    return 0;
}

优化效果说明

  • 空间上:每个区间只存储唯一值对应的表达式,避免了大量重复数据,空间复杂度从O(m²K)降至O(m²V),其中V是运算结果的可能取值数量(远小于K)。
  • 时间上:减少了大量重复计算和字符串操作,同时提前终止机制可以在找到解后立即停止,大幅提升大输入下的运行速度。
  • 正确性:由于优先遍历左侧分割点,且同一值只保留首次存入的表达式,确保了得到的是最左侧的合法括号化方式。

内容的提问来源于stack exchange,提问作者baptzera

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:47:32