设 $f_{i,j,k}$ 为第 $i$ 行状态为 $j$,$i-1$ 行状态为 $k$ 且 $1$ 至 $i-1$ 行全部有油库相邻的最小代价, $dp_{i,j,k}$ 为最小个数。
那么 $f$ 的转移方程显然:
$$f_{i,j,k}=\min_{0\leq l<2^m}\{f_{i-1,k,l}+sum_{i,j}\text{ }(\#)\}$$
其中 $sum_{i,j}$ 是第 $i$ 行状态为 $j$ 的花费, $dp$ 跟着 $f$ 转移一下就可以了。
$\#$ 是转移的条件,上面挤不下了,这里来写:
对于 $f_{i-1,k,l}$,第 $1$ 行至第 $i-2$ 行一定全部有油库相邻,只要考虑第 $i-1$ 列,所以 $\#$ 是 ```(j|k|l|k<<1|k>>1)```。
代码实现如下:
```cpp
#include <bits/stdc+.h>
using namespace std;
const int N = 10, M = 55;
int f[M][1 << N][1 << N], dp[M][1 << N][1 << N], sum[M][1 << N], a[M][N], n, m, ans1 = INT_MAX, ans2 = INT_MAX;
int main() {
cin >> n >> m;
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> a[i][j];
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) sum[i][j] += a[i][k + 1];
for (int i = 0; i < 1 << m; i++)
f[1][i][0] = sum[1][i], dp[1][i][0] = __builtin_popcount(i);
for (int i = 2; i <= n; i ++)
for (int j = 0; j < 1 << m; j ++)
for (int k = 0; k < 1 << m; k ++)
for (int l = 0; l < 1 << m; l ++)
if (((j | k | l | k << 1 | k >> 1) & (1 << m) - 1)== (1 << m) - 1) {
int t = f[i - 1][k][l] + sum[i][j], p = dp[i - 1][k][l] + __builtin_popcount(j);
if (f[i][j][k] > t || f[i][j][k] == t && dp[i][j][k] > p)
f[i][j][k] = t, dp[i][j][k] = p;
}
for (int i = 0; i < 1 << m; i ++)
for (int j = 0; j < 1 << m; j ++)
if (((i | j | i << 1 | i >> 1) & (1 << m) - 1) == (1 << m) - 1)
if (ans1 > f[n][i][j] || f[n][i][j] == ans1 && dp[n][i][j] < ans2)
ans1 = f[n][i][j], ans2 = dp[n][i][j];
cout << ans2 << ' ' << ans1 <<'\n';
system("pause")
return 0;
}
```