[题目解析]最小化团建熟悉程度和 | 豆包MarsCode AI刷题

122 阅读7分钟

该题主要使用豆包MarsCode AI刷题进行辅助解决

一、原题

问题描述

最近团队中新来了许多同事,小茗同学所在部门希望通过团建来促进新老成员的沟通交流,增进信任,同时提升团队协作力、执行力和竞争力。

当团建活动规模较大时,参与人数过多,一般会分成若干个小组以便于活动展开。然而,这也导致了不同小组的成员交流过少。为了缓解这个问题,团队提出了分布式团建的方法:将活动分成若干轮,每轮分成多个 3 人小组,每个小组自由支配活动经费单独活动。团队中的成员两两之间的熟悉程度互不相同,为了最大化降低成员之间的陌生程度,分组时需要考虑尽可能将不熟悉的成员匹配在一起,通过团建活动彼此熟络。每个 3 人小组的熟悉程度定义为小组内成员两两之间的熟悉程度之和,分组方案需最小化所有小组的熟悉程度之和。

作为一名算法工程师,小茗同学开始着手解决这个问题,但是遇到了一点小困难,想请你帮忙一起解决。

输入格式

第一行为一个整数 N,表示团队成员人数。 接下来 N 行,每行有 N 个整数 r_{i,j},表示成员 i 与成员 j 的熟悉程度。

输出格式

输出一个整数,表示将团队成员分成多个 3 人小组后,熟悉程度之和的最小值。

输入样例

  • 输入样例 1

3

100 78 97

78 100 55

97 55 100

  • 输入样例 2

6

100 56 19 87 38 61

56 100 70 94 88 94

19 70 100 94 43 95

87 94 94 100 85 11

38 88 43 85 100 94

61 94 95 11 94 100

输出样例

  • 输出样例 1

230

  • 输出样例 2

299

二、解决过程

一开始打算使用全排列解决,但是全排列的时间复杂度会非常高,提交时一直显示超时。全排列方法的思路:

  1. 生成所有排列

    • 将成员编号从 0N-1 进行全排列,得到所有可能的排列。
    • 每一个排列表示一个成员的排列顺序。
  2. 分组

    • 按照排列顺序,每 3 个人组成一个小组。
    • 计算每组的熟悉程度之和,并取最小值。
  3. 复杂度

    • 生成全排列的复杂度是 O(N!)
    • 对每个排列,分组计算熟悉程度的复杂度是 O(N)
    • 因此总复杂度是 O(N! * N),对于较大的 N(如 N > 9),复杂度会变得不可接受。

遂放弃,寻求AI帮助

image.png

AI解答如下:

### 问题理解
我们需要将团队成员分成若干个3人小组,每个小组的熟悉程度定义为小组内成员两两之间的熟悉程度之和。我们的目标是最小化所有小组的熟悉程度之和。

### 数据结构选择
-   输入是一个N x N的矩阵,表示成员之间的熟悉程度。
-   我们需要找到一种方法来将N个成员分成N/3个3人小组,使得所有小组的熟悉程度之和最小。

### 算法步骤
1.  **初始化**:定义一个变量来存储最小熟悉程度之和,初始值设为一个较大的数(例如1e9+7)。
1.  **组合生成**:生成所有可能的3人小组组合。由于N是3的倍数,我们可以直接生成所有可能的3人小组。
1.  **计算熟悉程度**:对于每一个3人小组,计算其熟悉程度之和。
1.  **更新最小值**:如果当前小组的熟悉程度之和小于之前的最小值,则更新最小值。
1.  **返回结果**:最终返回最小熟悉程度之和。

### 关键点
-   **组合生成**:可以使用递归或迭代的方式生成所有可能的3人小组组合。
-   **熟悉程度计算**:对于每一个3人小组,计算其熟悉程度之和。

### 进一步优化
-   由于N的范围是3到21,组合的数量可能较大,因此可以考虑使用剪枝策略来减少不必要的计算

然而AI提供的代码有误,原代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

int minFamiliarity = 1e9+7;

void backtrack(int start, int N, std::vector<std::vector<int>>& familiar_matrix, std::vector<bool>& used, int currentSum) {
    if (currentSum >= minFamiliarity) {
        // 剪枝:如果当前熟悉程度之和已经超过最小值,提前终止
        return;
    }
    if (start == N) {
        // 所有成员都已分组,更新最小值
        minFamiliarity = std::min(minFamiliarity, currentSum);
        return;
    }
    for (int i = start; i < N; ++i) {
        if (used[i]) continue;
        used[i] = true;
        for (int j = i + 1; j < N; ++j) {
            if (used[j]) continue;
            used[j] = true;
            for (int k = j + 1; k < N; ++k) {
                if (used[k]) continue;
                used[k] = true;
                // 计算当前3人小组的熟悉程度之和
                int groupSum = familiar_matrix[i][j] + familiar_matrix[i][k] + familiar_matrix[j][k];
                // 递归生成下一个3人小组
                backtrack(k + 1, N, familiar_matrix, used, currentSum + groupSum);
                used[k] = false;
            }
            used[j] = false;
        }
        used[i] = false;
    }
}

int solution(int N, std::vector<std::vector<int>> familiar_matrix) {
    std::vector<bool> used(N, false);
    backtrack(0, N, familiar_matrix, used, 0);
    return minFamiliarity;
}

int main() {
    std::vector<std::vector<int>> familiar_matrix1 = {{100, 78, 97}, {78, 100, 55}, {97, 55, 100}};
    std::vector<std::vector<int>> familiar_matrix2 = {
        {100, 56, 19, 87, 38, 61},
        {56, 100, 70, 94, 88, 94},
        {19, 70, 100, 94, 43, 95},
        {87, 94, 94, 100, 85, 11},
        {38, 88, 43, 85, 100, 94},
        {61, 94, 95, 11, 94, 100}};

    std::cout << (solution(3, familiar_matrix1) == 230) << std::endl;
    std::cout << (solution(6, familiar_matrix2) == 299) << std::endl;

    return 0;
}

遂寻求GPT-4o的帮助:GPT-4o给出的代码如下(经测试,为正确代码):

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

// 动态规划解决问题
int solution(int N, vector<vector<int>> familiar_matrix) {
    // 特殊情况,成员数必须是 3 的倍数
    if (N % 3 != 0) return -1;

    // 状态压缩DP:dp[mask] 表示当前状态下的最小熟悉程度之和
    int maxMask = 1 << N; // 2^N 种状态
    vector<int> dp(maxMask, INT_MAX);

    // 初始状态:空集合的熟悉度和为 0
    dp[0] = 0;

    // 遍历所有可能的状态
    for (int mask = 0; mask < maxMask; ++mask) {
        // 计算当前状态下已经选择的人数
        int selectedCount = __builtin_popcount(mask);
        if (selectedCount % 3 != 0) continue; // 必须是 3 的倍数,才可能形成完整的小组

        // 尝试选择 3 个未选中的人组成一个新小组
        for (int i = 0; i < N; ++i) {
            if (mask & (1 << i)) continue; // i 已经被选中

            for (int j = i + 1; j < N; ++j) {
                if (mask & (1 << j)) continue; // j 已经被选中

                for (int k = j + 1; k < N; ++k) {
                    if (mask & (1 << k)) continue; // k 已经被选中

                    // 计算新小组 (i, j, k) 的熟悉程度之和
                    int newGroupFamiliarity = familiar_matrix[i][j] + familiar_matrix[j][k] + familiar_matrix[i][k];

                    // 更新 dp
                    int newMask = mask | (1 << i) | (1 << j) | (1 << k);
                    dp[newMask] = min(dp[newMask], dp[mask] + newGroupFamiliarity);
                }
            }
        }
    }

    // 返回全选状态的最优值
    return dp[maxMask - 1];
}

int main() {
    // 测试用例 1
    vector<vector<int>> familiar_matrix1 = {
        {100, 78, 97},
        {78, 100, 55},
        {97, 55, 100}};
    cout << (solution(3, familiar_matrix1) == 230) << endl;

    // 测试用例 2
    vector<vector<int>> familiar_matrix2 = {
        {100, 56, 19, 87, 38, 61},
        {56, 100, 70, 94, 88, 94},
        {19, 70, 100, 94, 43, 95},
        {87, 94, 94, 100, 85, 11},
        {38, 88, 43, 85, 100, 94},
        {61, 94, 95, 11, 94, 100}};
    cout << (solution(6, familiar_matrix2) == 299) << endl;

    return 0;
}

之后又将豆包MarsCode AI的答案提交给了GPT-4o,修改后的代码 运行正确但依旧存在超时现象,手动做了一些小修改,再提交,依旧超时,遂放弃沿用豆包MarsCode AI的思路。

总结

换个LLM用吧