Skip to content

S2OJ - 45. 机房的新生活委员

检测到 KaTeX 加载失败,可能会导致文中的数学公式无法正常渲染。

#题面

#题目描述

大家都知道,R_rank_Pyram 在两次搜索专题比赛中都雄踞排行榜首位。林老师会给他个什么官当当呢?其实讲真,老师也很矛盾……直到有一天,lgj 发现机房的一些日用品需要更新了,比如扫把啦,抹布啦,畚斗啦……那不然就奖励 R_rank_Pyram 同学当机房的新生活委员吧!

新官上任三把火,R_rank_Pyram 一上台就要为机房购置 mm 种日用品(编号从 11mm),R_rank_Pyram 会选择在 nn 家商店购买。由于 R_rank_Pyram 不想带着买好的商品在商店间穿梭,他每次从机房出发到一家店铺买完东西后,会把东西带回机房,再出发去另一家店铺……

已知从机房往返第 ii 家店铺的交通费为 feei\mathit{fee}_i,并且每件商品在不同店铺购买价格也不同,第 ii 件商品在第 jj 家店铺的价格为 costi,j\mathit{cost}_{i, j}。精打细算的生活委员想知道,购买这 mm 件商品最少需要花多少钱?

#输入格式

第一行包含两个正整数 n,mn, m,表示店铺数量和需要购买的物品种类数。

接下来 nn 行,每行第一个正整数 feei\mathit{fee}_i 表示机房到第 ii 家商店的往返路费,接下来 mm 个正整数,依次表示 costi,j\mathit{cost}_{i, j}

#输出格式

输出一个正整数,即最小花费。

#样例输入输出

样例输入 #1

3 4
5 7 3 7 9
2 1 20 3 2
8 1 20 1 1

样例输出 #1

16

样例解释 #1

生活委员会在 11 号店买 22 号商品,在 22 号店买 1,3,41, 3, 4 号商品。

#数据规模与约定

对于 40%40\% 的数据:n,m10n, m \leq 10
对于 60%60\% 的数据,n50n \leq 50
对于 100%100\% 的数据,1n1001 \leq n \leq 1001m161 \leq m \leq 161feei1061 \leq \mathit{fee}_i \leq 10^61costi,j1061 \leq \mathit{cost}_{i, j} \leq 10^6

#思路

一看数据范围就知道是一个很显然的状压 DP。

对于每个状态可以从三种情况转移得出答案:

  1. 保持当前购买情况不变,没有新增开销;
  2. 上一个物品也是在当前商店内购买的,直接加上物品 kk 的价格;
  3. 从其他商店赶来后再购买物品 kk,除了物品价格外还需要支付路费。

每轮转移完成后与之前的答案比较,取最优的存储即可。

详细实现可以查看下方代码。

#代码

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <algorithm>
#include <cstring>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 105,
M = 17;

int n, m, fee[N], cost[N][M], f[N][1 << M], ans = 0x3f3f3f3f;

int main() {
std::ios::sync_with_stdio(false);
cin.tie(nullptr);

cin >> n >> m;

for (int i = 1; i <= n; i++) {
cin >> fee[i];

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

memset(f, 0x3f, sizeof(f));
f[0][0] = 0;

for (int i = 1; i <= n; i++) {
for (int j = 0; j < 1 << m; j++) {
for (int k = 0; k < m; k++) {
if (j & (1 << k)) continue; // 已经购买过物品 k 则跳过

f[i][j | (1 << k)] = std::min({
f[i][j | (1 << k)], // 保持当前购买情况不变,没有新增开销
f[i][j] + cost[i][k], // 上一个物品也是在当前商店内购买的,直接加上物品 k 的价格
f[i - 1][j] + fee[i] + cost[i][k], // 从其他商店赶来后再购买物品 k,除了物品价格外还需要支付路费
});
}
}

for (int j = 0; j < 1 << m; j++) {
f[i][j] = std::min(f[i][j], f[i - 1][j]); // 与之前的答案比较,取最优的
}
}

cout << f[n][(1 << m) - 1] << endl;

return 0;
}