AcWing 275. 传纸条 (dp 状态优化)

12 篇文章 0 订阅
9 篇文章 0 订阅

AcWing 275. 传纸条

题目

给一个 m 行 n 列矩阵,当前位置在(1,1),需要走到(m,n)然后再走回来,走到一个点可以拿走当前的值,但是每个点只能走一次,求最后能得到的最大值。

分析

如果只有一条路线就很简单了。现在要回去,可以看成同时从(1,1)走两条不相交的路线,状态表示可以用 d p [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] dp[x1][y1][x2][y2] dp[x1][y1][x2][y2] 从 (1,1) 分别走到两个点。

进一步优化状态,可以记录走了 k 步,和两点的 x 坐标。

①: 状态表示(经验)

  1. 集合: d p [ k ] [ x 1 ] [ x 2 ] dp[k][x1][x2] dp[k][x1][x2] 表示所有两条路线从 (1,1) 分别走到 ( x 1 , k − x 1 ) , ( x 2 , k − x 2 ) (x1, k-x1), (x2, k-x2) (x1,kx1),(x2,kx2)的所有方案(不相交)
  2. 属性:表示集合中所有方案路径花费之和的最大值

②: 状态转移

两条路线分别可以从右、下走到当前位置 d p [ k ] [ x 1 ] [ x 2 ] dp[k][x1][x2] dp[k][x1][x2],排列组合一下就是 4 种方式。

推广:如果这个题要走 k 条路径的话,就不能用 dp 来做了,状态太多,要用最小费用最大流

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int N = 50 + 5;

int n, m, t;
int w[N][N];
int dp[N * 2][N][N];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) 
			scanf("%d", &w[i][j]);

	for (int k = 2; k <= n + m; k++) 
		for (int x1 = max(1, k - m); x1 <= min(k - 1, n); x1++) 
			for (int x2 = max(1, k - m); x2 <= min(k - 1, n); x2++) {
				if (x1 == x2) continue;     // 不能走相同的点
				if (x1 != x2) t = w[x1][k - x1] + w[x2][k - x2];
				for (int a = 0; a < 2; a++) 		// 四个方向
					for (int b = 0; b < 2; b++)
						dp[k][x1][x2] = max(dp[k][x1][x2], dp[k - 1][x1 - a][x2 - b] + t);
			}
	// 最后一个点的值 加上 两个方向来的最大值
	printf("%d\n", w[n][m] + max(dp[n + m - 1][n][n - 1], dp[n + m - 1][n - 1][n]));
	return 0;
}


代码其实也可以写成,其中如果两人在相同格子,则 t 等于这个格子的分值;否则等于两个格子的分值之和。主要是为了计算最后一个点,中间走相同点的肯定会被其他值替换,所以没事。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int N = 50 + 5;

int n, m;
int w[N][N];
int dp[N * 2][N][N];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) 
			scanf("%d", &w[i][j]);

	for (int k = 2; k <= n + m; k++) 
		for (int x1 = max(1, k - m); x1 <= min(k - 1, n); x1++) 
			for (int x2 = max(1, k - m); x2 <= min(k - 1, n); x2++) {
				int t = w[x1][k - x1];
				if (x1 != x2) t += w[x2][k - x2];
				for (int a = 0; a < 2; a++) 		// 四个方向
					for (int b = 0; b < 2; b++)
						dp[k][x1][x2] = max(dp[k][x1][x2], dp[k - 1][x1 - a][x2 - b] + t);
			}
	printf("%d\n", dp[n + m][n][n]);
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值