最小化团建熟悉程度和
问题描述
小U加入了一个团队,团队决定举办分布式团建活动来加强新老成员间的交流。为了最大化新旧成员间的互动,需要将团队成员分成若干个由三人组成的小组,使得每个小组内成员间的熟悉程度之和最小。
每个小组的熟悉程度定义为小组内成员两两之间的熟悉程度之和。小U负责计算出最优的分组方式。
矩阵familiar_matrix[i][j]表示第i个人和第j个人之间的熟悉程度。保证两人彼此的熟悉程度是一样的。
保证人数一定是3的倍数。
测试样例
样例1:
输入:
N = 3,familiar_matrix = [[100, 78, 97], [78, 100, 55], [97, 55, 100]]
输出:230
样例2:
输入:
N = 6,familiar_matrix = [[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]]
输出:299
题目解析
解决这个问题的核心在于如何高效地找到所有成员分组后最小的熟悉程度之和
难点1
怎么使用一个位掩码来表示哪些成员已经被分组,并使用一个数组来存储每个状态的最小熟悉程度之和。
核心思想
-
状态压缩动态规划(Bitmask DP) :
- 使用一个整数
mask来表示当前哪些成员已经被分组。mask的第i位为 1 表示第i个成员已经被分组,为 0 表示未被分组。 - 使用一个数组
dp[mask]来记录当前状态下最小的熟悉程度之和,避免重复计算。
- 使用一个整数
-
预处理:
- 预先计算每个三人组合的熟悉程度之和,存储在一个字典或数组中,以减少在动态规划过程中的计算量。
-
动态规划:
- 遍历所有可能的
mask状态,尝试将当前未分组的成员组成新的小组,并更新dp数组。 - 使用剪枝技术,如果当前的熟悉程度之和已经超过了已知的最小值,则提前返回,避免不必要的计算。
- 遍历所有可能的
具体步骤
-
初始化:
- 初始化
dp数组,dp[0] = 0,表示初始状态(没有成员被分组)的熟悉程度之和为 0。 - 其他状态的
dp值初始化为无穷大(Long.MAX_VALUE或float('inf'))。
- 初始化
-
预处理:
- 遍历所有可能的三人组合,计算每个组合的熟悉程度之和,并存储在
groupFamiliarity字典中。
- 遍历所有可能的三人组合,计算每个组合的熟悉程度之和,并存储在
-
动态规划:
- 遍历所有可能的
mask状态。 - 如果当前
mask状态的dp值为无穷大,跳过该状态。 - 找到下一个未分组的成员
nextMember。 - 尝试将
nextMember与另外两个未分组的成员组成一个小组,更新dp数组。
- 遍历所有可能的
-
结果:
- 最终结果为
dp[(1 << N) - 1],即所有成员都被分组时的最小熟悉程度之和。
- 最终结果为
代码实现
下面是python版本代码实现:
def solution(N, familiar_matrix):
# 初始化 dp 数组
dp = [float('inf')] * (1 << N)
dp[0] = 0
# 预处理每个小组的熟悉程度之和
group_familiarity = {}
for i in range(N):
for j in range(i + 1, N):
for k in range(j + 1, N):
group_familiarity[(i, j, k)] = familiar_matrix[i][j] + familiar_matrix[i][k] + familiar_matrix[j][k]
# 动态规划
for mask in range(1 << N):
if dp[mask] == float('inf'):
continue
# 找到下一个未分组的成员
next_member = -1
for i in range(N):
if not (mask & (1 << i)):
next_member = i
break
if next_member == -1:
continue
# 尝试将 next_member 与另外两个未分组的成员组成一个小组
for j in range(next_member + 1, N):
if not (mask & (1 << j)):
for k in range(j + 1, N):
if not (mask & (1 << k)):
new_mask = mask | (1 << next_member) | (1 << j) | (1 << k)
group_familiarity_sum = group_familiarity[(next_member, j, k)]
if dp[mask] + group_familiarity_sum < dp[new_mask]:
dp[new_mask] = dp[mask] + group_familiarity_sum
return dp[(1 << N) - 1]
# 测试样例
familiar_matrix1 = [[100, 78, 97],
[78, 100, 55],
[97, 55, 100]
]
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]
]
familiar_matrix3 = [[100, 2, 9, 8, 3, 12, 6, 12, 6, 18, 3, 14, 1, 9, 8, 3, 14, 4],
[2, 100, 6, 6, 7, 6, 5, 12, 7, 16, 10, 2, 5, 16, 1, 11, 7, 1],
[9, 6, 100, 14, 16, 12, 9, 1, 9, 8, 7, 2, 6, 1, 5, 5, 9, 12],
[8, 6, 14, 100, 4, 5, 10, 9, 4, 4, 10, 6, 13, 16, 9, 8, 5, 15],
[3, 7, 16, 4, 100, 8, 1, 2, 5, 11, 10, 7, 1, 1, 10, 14, 11, 4],
[12, 6, 12, 5, 8, 100, 7, 3, 15, 7, 10, 12, 4, 9, 7, 5, 15, 2],
[6, 5, 9, 10, 1, 7, 100, 7, 2, 6, 6, 6, 4, 2, 3, 10, 6, 17],
[12, 12, 1, 9, 2, 3, 7, 100, 3, 14, 12, 5, 9, 5, 7, 9, 6, 6],
[6, 7, 9, 4, 5, 15, 2, 3, 100, 11, 10, 1, 1, 15, 11, 8, 10, 5],
[18, 16, 8, 4, 11, 7, 6, 14, 11, 100, 5, 14, 5, 15, 3, 2, 1, 6],
[3, 10, 7, 10, 10, 10, 6, 12, 10, 5, 100, 1, 10, 2, 4, 4, 9, 10],
[14, 2, 2, 6, 7, 12, 6, 5, 1, 14, 1, 100, 14, 2, 13, 2, 7, 9],
[1, 5, 6, 13, 1, 4, 4, 9, 1, 5, 10, 14, 100, 1, 3, 7, 14, 12],
[9, 16, 1, 16, 1, 9, 2, 5, 15, 15, 2, 2, 1, 100, 3, 3, 7, 13],
[8, 1, 5, 9, 10, 7, 3, 7, 11, 3, 4, 13, 3, 3, 100, 1, 2, 2],
[3, 11, 5, 8, 14, 5, 10, 9, 8, 2, 4, 2, 7, 3, 1, 100, 8, 1],
[14, 7, 9, 5, 11, 15, 6, 6, 10, 1, 9, 7, 14, 7, 2, 8, 100, 4],
[4, 1, 12, 15, 4, 2, 17, 6, 5, 6, 10, 9, 12, 13, 2, 1, 4, 100]
]
print(solution(3, familiar_matrix1) == 230) # 输出: True
print(solution(6, familiar_matrix2) == 299) # 输出: True
print(solution(18, familiar_matrix3)) # 输出: 387
java 代码使用了类似的逻辑,包括预处理和动态规划。
注意事项:
- 类型转换:在 Java 中,
int类型的范围是有限的,因此使用long类型来存储dp数组的值,以防止溢出。 - 字符串键:在
groupFamiliarity字典中,使用字符串作为键,格式为"i,j,k",以便于查找和存储三人组合的熟悉程度之和。
import java.util.HashMap;
import java.util.Map;
public class Main {
public static int solution(int N, int[][] familiarMatrix) {
// 初始化 dp 数组
long[] dp = new long[1 << N];
for (int i = 1; i < (1 << N); i++) {
dp[i] = Long.MAX_VALUE;
}
dp[0] = 0;
// 预处理每个小组的熟悉程度之和
Map<String, Integer> groupFamiliarity = new HashMap<>();
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
for (int k = j + 1; k < N; k++) {
int sum = familiarMatrix[i][j] + familiarMatrix[i][k] + familiarMatrix[j][k];
groupFamiliarity.put(i + "," + j + "," + k, sum);
}
}
}
// 动态规划
for (int mask = 0; mask < (1 << N); mask++) {
if (dp[mask] == Long.MAX_VALUE) {
continue;
}
// 找到下一个未分组的成员
int nextMember = -1;
for (int i = 0; i < N; i++) {
if ((mask & (1 << i)) == 0) {
nextMember = i;
break;
}
}
if (nextMember == -1) {
continue;
}
// 尝试将 nextMember 与另外两个未分组的成员组成一个小组
for (int j = nextMember + 1; j < N; j++) {
if ((mask & (1 << j)) == 0) {
for (int k = j + 1; k < N; k++) {
if ((mask & (1 << k)) == 0) {
int newMask = mask | (1 << nextMember) | (1 << j) | (1 << k);
int groupFamiliaritySum = groupFamiliarity.get(nextMember + "," + j + "," + k);
dp[newMask] = Math.min(dp[newMask], dp[mask] + groupFamiliaritySum);
}
}
}
}
}
return (int) dp[(1 << N) - 1];
}
public static void main(String[] args) {
int[][] familiarMatrix1 = {
{100, 78, 97},
{78, 100, 55},
{97, 55, 100}
};
int[][] familiarMatrix2 = {
{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}
};
System.out.println(solution(3, familiarMatrix1) == 230); // 输出: true
System.out.println(solution(6, familiarMatrix2) == 299); // 输出: true
}
}