【题意】
你要购买 m m m种物品各一件,一共有 n n n家商店,你到第i家商店的路费为 d [ i ] d[i] d[i],在第 i i i家商店购买第 j j j种物品的费用为 c [ i ] [ j ] c[i][j] c[i][j],求最小总费用。
【题解】
很容易想到状态转移方程。设
f
[
i
]
[
S
]
f[i][S]
f[i][S]为去了前
i
i
i家商店,已购买物品的集合为S,则:
f
[
i
]
[
S
]
=
m
i
n
{
f
[
i
−
1
]
[
S
]
,
f
[
i
−
1
]
[
S
′
]
∣
S
′
S
}
f[i][S]=min\{f[i-1][S], f[i-1][S']|S' \varsubsetneqq S \}
f[i][S]=min{f[i−1][S],f[i−1][S′]∣S′S}
枚举子集的复杂度为
O
(
3
m
)
O(3^m)
O(3m),故总的复杂度为
O
(
n
3
m
)
O(n3^m)
O(n3m)。目测会T。
如果我们把每一个商店看成一个物品,它卖的东西看成这个物品被使用的状态。若把这个状态看成一个二进制位,则每一位若为
1
1
1则有一个权值(相应商品的价格),那么抛开路费,在一家商店里的转移就类似于完全背包。
实现时,我们可以将上一行的状态搬下来并加上
d
[
i
]
d[i]
d[i],然后进行同层的完全背包转移,再与不购买物品的选择作比较取最小值。
此题中,单层完全背包的时间复杂度为
O
(
n
m
)
O(nm)
O(nm),故总时间复杂度为
O
(
n
m
2
m
)
O(nm2^m)
O(nm2m)。
【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mm = 17, mn = 105;
int f[mn][1 << mm], d[mn];
int c[mn][mm];
int main()
{
int n, m, i, j, k;
scanf("%d%d", &n, &m);
for(i = 1; i <= n; i++)
{
scanf("%d", &d[i]);
for(j = 0; j < m; j++)
scanf("%d", &c[i][j]);
}
memset(f, 0x3f, sizeof f);
int lim = 1 << m;
f[0][0] = 0;
for(i = 1; i <= n; i++)
{
for(j = 0; j < lim; j++)
f[i][j] = f[i - 1][j] + d[i];
for(k = 0; k < m; k++)//枚举物品
for(j = 1; j < lim; j++)
if(j & (1 << k))
f[i][j] = min(f[i][j], f[i][j ^ (1 << k)] + c[i][k]);
for(j = 0; j < lim; j++)
f[i][j] = min(f[i][j], f[i - 1][j]);
}
printf("%d\n", f[n][lim - 1]);
}