题意:n行m列的网格蛋糕上有一些樱桃,每次可以用刀沿着网格线切成两块,问要想使最终每块蛋糕上恰好有一颗樱桃,切得最小长度。
直接正向去想怎样切感觉不好想,但是可以反过来,像区间拼接一样考虑把当个网格的蛋糕拼接起来,拼接的长度就是要切的长度。
dp[a][b][c][d]表示从(a, b)点到(c, d)的矩形范围内拼接的最短长度,num[a][b][c][d]用来统计这个矩形区域内的樱桃数量。如果这一块区域内只有一块(或者没有)樱桃那么拼接长度一定是0。对于一块区域可以是上下两个矩形也可以是左右两个矩形拼接起来的,那么枚举拼接的位置就可以了,注意如果拼接的两个矩形有一个中没有樱桃是不符合条件的,要跳过。
整个思想和区间dp一致,只是区间dp是一维的,而这个是二维的。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
int dp[21][21][21][21];
int num[21][21][21][21];
int main() {
int ks = 1, n, m, q, r, c;
while(~scanf("%d%d%d", &n, &m, &q)) {
memset(num, 0, sizeof(num));
memset(dp, 0x3f, sizeof(dp));
while(q--) {
scanf("%d%d", &r, &c);
num[r - 1][c - 1][r][c] = 1;
}
for(int rl = 1; rl <= n; rl++) {
for(int cl = 1; cl <= m; cl++) {
for(int br = 0; br + rl <= n; br++) {
for(int bc = 0; bc + cl <= m; bc++) {
int er = br + rl, ec = bc + cl;
num[br][bc][er][ec] = max(num[br][bc][br + 1][ec] + num[br + 1][bc][er][ec], num[br][bc][er][bc + 1] + num[br][bc + 1][er][ec]);
// printf("%d %d %d %d %d\n", br, bc, er, ec, num[br][bc][er][ec]);
if(num[br][bc][er][ec] <= 1) {
dp[br][bc][er][ec] = 0;
continue;
}
for(int j = br + 1; j < er; j++) {
if(!num[br][bc][j][ec] || !num[j][bc][er][ec]) continue;
if(dp[br][bc][j][ec] + dp[j][bc][er][ec] + cl < dp[br][bc][er][ec]) {
dp[br][bc][er][ec] = dp[br][bc][j][ec] + dp[j][bc][er][ec] + cl;
}
}
for(int j = bc + 1; j < ec; j++) {
if(!num[br][bc][er][j] || !num[br][j][er][ec]) continue;
if(dp[br][bc][er][ec] > dp[br][bc][er][j] + dp[br][j][er][ec] + rl)
dp[br][bc][er][ec] = dp[br][bc][er][j] + dp[br][j][er][ec] + rl;
}
}
}
}
}
printf("Case %d: %d\n", ks++, dp[0][0][n][m]);
}
return 0;
}
/**
3 4 3
1 2
2 3
3 2
**/