寻宝游戏,五维dp
Problem Description
小Q最近迷上了一款寻宝游戏,这款游戏中每局都会生成一个n×m的网格地图,从上往下依次编号为第1行到第n行,从左往右依次编号为第1列到第m列。每个格子上都有不同数量的金币,第i行第j列的格子上的金币数量为ai,j。
小Q一开始位于(1,1),每次他可以往右或者往下走,每当他经过某个格子时,他就可以拿走这个格子上的所有金币。小Q不能走出这个地图,当小Q不能再行动时,游戏结束。显然当且仅当小Q位于(n,m)时,游戏才会结束。
一轮游戏的得分为这一轮中收集到的金币总量,而在游戏开始前,因为小Q是超级VIP用户,所以他有k次机会交换某两个格子中的金币数。这k次机会不一定要用完,请写一个程序帮助小Q在一轮内拿到尽可能多的金币。
Input
第一行包含一个正整数T(1≤T≤10),表示测试数据的组数。
每组数据第一行包含三个整数n,m,k(2≤n,m≤50,0≤k≤20),分别表示地图的长宽以及交换的次数。
接下来n行,每行m个整数ai,j(0≤ai,j≤106),依次表示每个格子中金币的数量。
Output
对于每组数据,输出一行一个整数,即能收集到的金币数量的最大可能值。
Sample Input
2
3 4 0
1 2 3 4
9 8 7 6
5 4 7 2
5 5 1
9 9 9 0 0
0 0 9 0 0
0 0 0 0 0
0 0 9 0 0
9 0 9 9 9
Sample Output
34
81
解析:
这道题是一道dp题(看出来的,感觉)
DP的核心思想是用集合来表示一类方案,然后从集合的维度来考虑状态之间的递推关系。
状态计算对应集合的划分,将问题划分为不重不漏的子集:
这里存在一种划分方式,将问题划分为 f[i][j][x][y] 4个不重不漏的子集
其中:f[i][j][x][y] 表示从(1,1)走到当前(i,j)位置,路线中有 x 个点未选,再一定不经过的区域有 y 个点被选的最大价值
这里 f[i][j][x][y] 可以从以下几种状态转移而来
f[i-1][j][x][y]+mp[i][j] //从上一行转移而来
f[i-1][j][x-1][y] //从上一行转移而来,且当前的 mp[i][j] 点不选
f[i][j-1][x][y]+mp[i][j] //从当前这一点的左边转移而来
f[i][j-1][x-1][y] //从当前这一点的左边转移过来,且当前的 mp[i][j] 点不选
最后还有一钟不容易想到的转移情况:
f[i][j][x][y-r]+a[r]
a[r]为所有当前这一行和上一行不会经过的点,重大到小按顺序排列后,再求前缀和
这种情况更新了前面都没有更新过的情况,即第 y 维;
但需注意当前:这种情况只能是在从上一行往下转移后才能才能更新,否则集合数组a中的情况可能会包含在想右转移的过程中
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 55;
int n, m, kk;
int mp[N][N];
int f[N][N][N][N],a[N];
int cmp(const int& a, const int& b) {
return a > b;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &n, &m, &kk);
memset(f, -1, sizeof f);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &mp[i][j]);
}
}
f[1][1][0][0] = mp[1][1];
f[1][1][1][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int index = 0;
a[0] = 0;
for (int k = 1; k <= m; k++) {// 是从当前点往前和上一行当前点往后
if (k < j)
a[++index] = mp[i][k];
else if (k > j)
a[++index] = mp[i-1][k];
}
sort(a + 1, a + 1 + index, cmp); //从大到小排序 将那些不是必经之路上的点取出来
for (int k = 1; k <= index; k++) {//求个前缀和
a[k] = a[k - 1] + a[k];
}
for (int y = 0; y <= kk; y++) {
for (int x = 0; x <= kk; x++) {
if (f[i - 1][j][y][x] != -1) {// 从上边过来(经过的路上)
f[i][j][y][x] = max(f[i][j][y][x], f[i - 1][j][y][x] + mp[i][j]);
}
if ( y >= 1&&f[i-1][j][y-1][x] != -1) {// 从上边过来(经过的路上),但不取当前mp[i][j]的点
f[i][j][y][x] = max(f[i][j][y][x], f[i-1][j][y-1][x]);
}
}
}
for (int x = kk; x >= 0; x--) {
for (int y = kk; y >= 0; y--) {
for (int r = y; r >= 0; r--) {
if (y >= r && f[i][j][x][y - r] != -1)//取出来r个
f[i][j][x][y] = max(f[i][j][x][y], f[i][j][x][y - r] + a[r]);
}
}
}
for (int x = 0; x <= kk; x++) {
for (int y = 0; y <= kk; y++) {
if (f[i][j - 1][x][y] != -1)
f[i][j][x][y] = max(f[i][j][x][y], f[i][j - 1][x][y] + mp[i][j]);
if (f[i][j - 1][x - 1][y] != -1&&x>=1) {
f[i][j][x][y] = max(f[i][j][x][y], f[i][j - 1][x - 1][y]);
}
}
}
}
}
int ans = 0;
for (int i = 0; i <= kk; i++) {
ans = max(ans, f[n][m][i][i]);
}
cout << ans << endl;
}
return 0;
}