文章目录
1. 题目描述
1.1. Limit
Time Limit: 2000 ms
Memory Limit: 131072 kB
1.2. Problem Description
给你两组点,其中第一组中有 size1
个点,第二组中有 size2
个点,且 size1 >= size2
。
任意两点间的连接成本 cost
由大小为 size1 x size2
矩阵给出,其中 cost[i][j]
是第一组中的点 i
和第二组中的点 j
的连接成本。如果两个组中的每个点都与另一组中的一个或多个点连接,则称这两组点是连通的。换言之,第一组中的每个点必须至少与第二组中的一个点连接,且第二组中的每个点必须至少与第一组中的一个点连接。
返回连通两组点所需的最小成本。
1.3. Sample Input 1
输入:cost = [[15, 96], [36, 2]]
1.4. Sample Output 1
输出:17
解释:连通两组点的最佳方法是:
1--A
2--B
总成本为 17 。
1.5. Sample Input 2
输入:cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]]
1.6. Sample Output 2
输出:4
解释:连通两组点的最佳方法是:
1--A
2--B
2--C
3--A
最小成本为 4 。
请注意,虽然有多个点连接到第一组中的点 2 和第二组中的点 A ,但由于题目并不限制连接点的数目,所以只需要关心最低总成本。
1.7. Source
2. 解读
状态压缩DP
。
2.1. 解法一
以输入 cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]]
为例。
dp[i][state]
表示 前 i
行 在每一行都至少有一条边连接的情况下
构成连接状态 state
的最小成本。
( i , s t a t e ) (i, state) (i,state) | ( 001 ) B (001)_B (001)B | ( 010 ) B (010)_B (010)B | ( 011 ) B (011)_B (011)B | ( 100 ) B (100)_B (100)B | ( 101 ) B (101)_B (101)B | ( 110 ) B (110)_B (110)B | ( 111 ) B (111)_B (111)B |
---|---|---|---|---|---|---|---|
1 | 1 | 3 | 4 | 5 | 6 | 8 | 9 |
2 | 5 | 4 | 2 | 6 | 2 | 4 | 3 |
3 | 6 | 9 | 3 | 9 | 3 | 7 | 4 |
表1 状态示例 \text{表1 状态示例} 表1 状态示例
首先对每一行 i i i 的每个状态 s t a t e state state 进行遍历,连接下一行的某一条边,得到连接成下一个状态的最小成本。
temp[nextState] = min(temp[nextState], dp[state] + cost[i][j]
接下来对每个状态 s t a t e state state 进行反转,得到反转后的状态 f l i p S t a t e flipState flipState,然后枚举 f l i p S t a t e flipState flipState 的子状态 $subState = w & flipState $, $ w \in [1, flipState]$,将子状态和原状态相或,得到新状态 n e w S t a t e = s t a t e ∣ s u b S t a t e newState = state | subState newState=state∣subState,尝试根据 n e w S t a t e newState newState 连接新的边,得到新边的权值 s u m sum sum,取最小值。
temp[nextState] = min(temp[nextState], dp[state] + sum)
2.2. 解法二
dp[i][s]
表示左边前 i
个点,右边点连接状况为 s
的最小 cost
。
三重循环,和Floyd
算法的思想有点类似。
Floyd
算法是遍历每个点,判断把这个点作为某条路径的中间结点是否能缩短路径长度。
这里是遍历右边的每个点,判断把它和 i - 1
点 相连的最小成本,即将 cost[i - 1][j]
与 dp[i - 1][s]
或 dp[i][s]
相加所得的和的最小值。
比如表1
中的 dp[2][(111)B]
,可以由 dp[1][(011)B]
+ cost[1][2]
得到,也可以由 dp[2][(011)B]
+ cost[1][2]
得到,由于 dp[2][(011)B] < dp[1][(011)B]
,最终结果为3
。
for (int i = 1; i <= n; i++) // 枚举左边每个点
for (int s = 0; s < lim; s++) // 枚举右边的连接状态
for (int j = 0; j < m; j++) // 枚举连接右边的点
// 连接一条边,得到新状态
dp[i][s | (1 << j)] = min({ dp[i][s | (1 << j)],
dp[i - 1][s] + cost[i - 1][j], // 用前i-1个点连成s状态,进行递推
dp[i][s] + cost[i - 1][j] }); // 用前i个点连成s状态,进行递推
3. 代码
代码参考自一位题友的题解。
// 解法一
class Solution {
public:
int connectTwoGroups(vector<vector<int>>& cost)
{
int size1 = cost.size(), size2 = cost[0].size(), stateNum = 1 << size2; //stateNum为第二组总的状态数+1
vector<int> dp(stateNum, INT_MAX); //dp数组初始化为很大的数
dp[0] = 0; //初始状态
for (int i = 0; i < size1; ++i) { //迭代每一行
vector<int> temp(stateNum, INT_MAX); //滚动数组
for (int state = 0; state < stateNum; ++state) { //枚举所有状态
if (dp[state] == INT_MAX)
continue; //若状态不可达,continue
for (int j = 0; j < size2; ++j) { //方案一:任选一条边相连
int nextState = state | (1 << j); //相连后到达的状态
temp[nextState] = min(temp[nextState], dp[state] + cost[i][j]); //更新最小花费
}
int flipState = (stateNum - 1) ^ state; //方案二:连接若干未连接的边,使用异或进行位反转得到所有未连接的边
for (int subState = flipState; subState; subState = flipState & (subState - 1)) { //枚举未连接的边的子集
int nextState = state | subState; //相连后到达的状态
if (nextState > state) { // 若添加了新边
int sum = 0; //记录花费
for (int k = 0; k < size2; ++k) //枚举size2
if (subState & (1 << k))
sum += cost[i][k]; //若子集中存在该边,则更新花费
temp[nextState] = min(temp[nextState], dp[state] + sum); //更新最小花费
}
}
}
dp = move(temp); //滚动数组
}
return dp.back(); //返回结果
}
};
代码参考自另一位题友的题解。
// 解法二
int dp[15][1 << 12 + 5];
class Solution {
public:
int connectTwoGroups(vector<vector<int>>& cost)
{
int n = cost.size(), m = cost[0].size();
int lim = 1 << m;
// 初始化
for (int i = 0; i <= n; i++)
for (int s = 0; s < lim; ++s)
dp[i][s] = INT_MAX;
dp[0][0] = 0;
// DP
for (int i = 1; i <= n; i++) // 枚举左边每个点
for (int s = 0; s < lim; s++) // 枚举右边的连接状态
for (int j = 0; j < m; j++) // 枚举连接右边的点
// 连接一条边,得到新状态
dp[i][s | (1 << j)] = min({ dp[i][s | (1 << j)],
dp[i - 1][s] + cost[i - 1][j], // 用前i-1个点连成s状态,进行递推
dp[i][s] + cost[i - 1][j] }); // 用前i个点连成s状态,进行递推
return dp[n][lim - 1];
}
};
联系邮箱:curren_wong@163.com
CSDN:https://me.csdn.net/qq_41729780
知乎:https://zhuanlan.zhihu.com/c_1225417532351741952
公众号:复杂网络与机器学习
欢迎关注/转载,有问题欢迎通过邮箱交流。