洛谷 P8488 「Wdoi-(-1)」恋弹者们的黑集市 的题解

题目大意

题目传送门

自己理解一下吧,略。

大体思路

显而易见可以想出是 dp。

dp 的转移方程和含义大佬们都讲得很清楚了,我不细讲。不过我会讲一些细节问题。

我们看一看 Lucas_Long 大佬的原话:

显然,当确定了一个骰子的两个面时,就可以确定这个骰子,因此我们可以将暴力 dp 降维。

有人问我为什么只要两个面就能确定整个立方体呢?其实要根据后面的内容。

上面根据他的 b b b 数组确定了下面,同理,前面确定了后面。又得根据 r r r 数组,于是,由上面和前面通过 r r r 数组就知道了右面。

其中 b b b r r r 数组都要打表, b b b 还好,但是 r r r 要打很多,所以会觉得很烦人。但是如果你仔细看的话,会发现规律。画一个立方体,由于我们知道上方和前方,那么每次确定上方的一个面(共 6 6 6 个面),分情况考虑,再按照可能在前方的 4 4 4 个面(也就是与上方那个面相邻的面),依次模拟即可。即使你是小学生,只要空间想象还行,是完全可以模拟出来的(但我不保证你 dp 能写出来)qwq。

为了让大家更好的理解代码和思路,我的有些数组用了比较具体的英文,而且都有中文解释。代码如下:

#include <bits/stdc++.h>
using namespace std;
const int F = 1, B = 2, L = 3, R = 4, U = 5, D = 6; //六个方向 
//		  front  back   left   right  up     down
int n, m, w[7];
int a[1007][1007];
int dp[1007][1007][7][7];
int reverse_side[1007]; //一个面的反面 
int UF_to_R[1007][1007]; //知道上方和前方,求右方 
//upfront_to_right
//dp[i][j][k][l] 表示现在在 (i,j) 且骰子上方编号为 k,前方编号为 l 时的最大值 
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
	return x * f;
}
inline void sw(int x, int y, int z) { UF_to_R[x][y] = z; }
inline void init_reverse_side() {
	reverse_side[F] = B;
	reverse_side[B] = F;
	reverse_side[L] = R;
	reverse_side[R] = L;
	reverse_side[U] = D;
	reverse_side[D] = U;
}
inline void init_UF_to_R() { //自己画个立体图就知道了 
	//假设原立体图的前面(F)在上面 
	sw(F, L, D);
	sw(F, D, R);
	sw(F, R, U);
	sw(F, U, L);
	//假设原立体图的后面(B)在上面 
	sw(B, L, U);
	sw(B, U, R);
	sw(B, R, D);
	sw(B, D, L);
	//假设原立体图的左面(L)在上面 
	sw(L, F, U);
	sw(L, U, B);
	sw(L, B, D);
	sw(L, D, F);
	//假设原立体图的右面(R)在上面 
	sw(R, F, D);
	sw(R, D, B);
	sw(R, B, U);
	sw(R, U, F);
	//假设原立体图的上面(U)在上面 
	sw(U, F, R);
	sw(U, R, B);
	sw(U, B, L);
	sw(U, L, F);
	//假设原立体图的下面(D)在上面 
	sw(D, F, L);
	sw(D, L, B);
	sw(D, B, R);
	sw(D, R, F);
}
int main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			a[i][j] = read();
	for(int i = 1; i <= 6; i++) w[i] = read();
	init_reverse_side(); init_UF_to_R();
	memset(dp, -0x3f, sizeof(dp)); //由于要求最大值,所以赋值为极小值 
	dp[1][1][5][1] = a[1][1] * w[6]; //原先 5 在上面,1 在前面,6 在下面 
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			for(int k = 1; k <= 6; k++)
				for(int l = 1; l <= 6; l++) {
					if(UF_to_R[k][l] == 0) continue; //如果两个编号无法同时是上面和前面,则跳过 
					if(dp[i][j][k][l] == -0x3f3f3f3f) continue; //未更新,也跳过 
					//更新两个状态,是 “我到哪里去”的思路 
					dp[i + 1][j][reverse_side[l]][k] = max(dp[i + 1][j][reverse_side[l]][k], dp[i][j][k][l] + a[i + 1][j] * w[l]);
					//上面的是往前翻 
					dp[i][j + 1][reverse_side[UF_to_R[k][l]]][l] = max(dp[i][j + 1][reverse_side[UF_to_R[k][l]]][l], dp[i][j][k][l] + a[i][j + 1] * w[UF_to_R[k][l]]);
					//上面的是往右翻 
				}
	int num = -0x3f3f3f3f;
	for(int i = 1; i <= 6; i++)
		for(int j = 1; j <= 6; j++)
			num = max(num, dp[n][m][i][j]);
	cout << num;
	return 0;
}

看完这份代码,你有没有发现什么规律呢?

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值