寻宝游戏,五维dp

Problem - 6289 (hdu.edu.cn)

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;
}

posted @ 2023-10-17 16:53  Landnig_on_Mars  阅读(13)  评论(0编辑  收藏  举报  来源