多进程类DP 笔记(方格取数+传纸条)

P1004 [NOIP2000 提高组] 方格取数

P1004

显然,对于这道题,我们一开始能想到的解法肯定是跑两遍二维 DP

但是,这道题最大的特点就是,第一遍的决策会影响第二遍的判断,所以我们可以考虑两遍一起走,即用一个四维 dp 数组记录两遍同时走的最大的和.此时因为同时移动,就不存在先后关系了

设走到两个格子时能获得总收益为 b e n e f i t benefit benefit ,则状态转移方程为
d p [ i 1 ] [ j 1 ] [ i 2 ] [ j 2 ] = max ⁡ { d p [ i 1 − 1 ] [ j 1 ] [ i 2 − 1 ] [ j 2 ] d p [ i 1 ] [ j 1 − 1 ] [ i 2 − 1 ] [ j 2 ] d p [ i 1 − 1 ] [ j 1 ] [ i 2 ] [ j 2 − 1 ] d p [ i 1 ] [ j 1 − 1 ] [ i 2 ] [ j 2 − 1 ] + b e n e f i t dp[i_1][j_1][i_2][j_2] = \max \begin{cases} dp[i_1 - 1][j_1][i_2 - 1][j_2] \\ dp[i_1][j_1 - 1][i_2 - 1][j_2] \\ dp[i_1 - 1][j_1][i_2][j_2 - 1] \\ dp [i_1][j_1 - 1][i_2][j_2 - 1] \\ \end{cases} + benefit dp[i1][j1][i2][j2]=maxdp[i11][j1][i21][j2]dp[i1][j11][i21][j2]dp[i11][j1][i2][j21]dp[i1][j11][i2][j21]+benefit

当走到同一个格子时,由于只能取一次,所以
b e n e f i t = { a [ i 1 ] [ j 1 ] + a [ i 2 ] [ j 2 ] i 1 ≠ i 2   o r   j 1 ≠ j 2 a [ i 1 ] [ j 1 ] i 1 = i 2   a n d   j 1 = j 2 benefit = \begin{cases} a[i_1][j_1] + a[i_2][j_2] & i_1 \neq i_2 \ or \ j_1 \neq j_2 \\ a[i_1][j_1] & i_1 = i_2 \ and \ j_1 = j_2 \end{cases} benefit={a[i1][j1]+a[i2][j2]a[i1][j1]i1=i2 or j1=j2i1=i2 and j1=j2
四维DP代码如下

#include <bits/stdc++.h>
using namespace std;
int n;
int a[35][35];
int dp[35][35][35][35];
int main(){
	scanf("%d" ,&n);
	for( ; ;){
		int x, y, z;
		scanf("%d%d%d" ,&x ,&y ,&z);
		if(x == 0 && y == 0 && z == 0){
			break;
		}
		a[x][y] = z;
	}
	for(int i1 = 1; i1 <= n; ++i1){
		for(int j1 = 1; j1 <= n; ++j1){
			for(int i2 = 1; i2 <= n; ++i2){
				for(int j2 = 1; j2 <= n; ++j2){
					dp[i1][j1][i2][j2] = max(dp[i1 - 1][j1][i2 - 1][j2], max(dp[i1][j1 - 1][i2 - 1][j2], max(dp[i1 - 1][j1][i2][j2 - 1], dp[i1][j1 - 1][i2][j2 - 1])));
					if(i1 != i2 && j1 != j2){
						dp[i1][j1][i2][j2] += a[i1][j1] + a[i2][j2];
					}
					if(i1 == i2 && j1 == j2){
						dp[i1][j1][i2][j2] += a[i1][j1];
					}
				}
			}
		}
	}
	printf("%d" ,dp[n][n][n][n]);
	return 0;
}

或者这么写,可以严格保证同时走(貌似)

#include <bits/stdc++.h>
using namespace std;
int n;
int mapp[15][15], dp[15][15][15][15];
int main(){
	scanf("%d" ,&n);
	int u, v, w;
	while(~scanf("%d%d%d" ,&u ,&v ,&w)){
		if(u == 0 && v == 0 && w == 0){
			break;
		}
		mapp[u][v] = w;
	}
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= n; ++j){
			for(int k = 1; k <= n; ++k){
				for(int l = 1; l <= n; ++l){
					if(i + j == k + l){
						dp[i][j][k][l] = max(dp[i][j][k][l], dp[i - 1][j][k - 1][l]);
						dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j - 1][k - 1][l]);
						dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j - 1][k][l - 1]);
						dp[i][j][k][l] = max(dp[i][j][k][l], dp[i - 1][j][k][l - 1]);
						if(i == k && j == l){
							dp[i][j][k][l] += mapp[i][j];
						}else{
							dp[i][j][k][l] += mapp[i][j] + mapp[k][l];
						}
					}
				}
			}
		}
	}
	printf("%d" ,dp[n][n][n][n]);
	return 0;
}

P1006 [NOIP2008 提高组] 传纸条

P1006

这道题与方格取数题面上的不同是方格取数是从左上到右下走两遍而传纸条是左上到右下走一遍再从右下到左上走一遍,但其实本质是一样的

然而如果我们左上到右下走一遍再从右下到左上走一遍,就没有办法判断走过的格了,同一个方向走那就可以要求不允许同时到达某个点,以此来解决不能重复走的问题.从两个方向走只能保证中间那个位置不重复,前后是没有办法判重的,所以这道题状态转移方程与方格取数相同

注意最后的答案是 d p [ m − 1 ] [ n ] [ m ] [ n − 1 ] dp[m - 1][n][m][n - 1] dp[m1][n][m][n1] ,注释会详细说

#include <bits/stdc++.h>
using namespace std;
int m, n;
int a[55][55];
int dp[55][55][55][55];
int main(){
	scanf("%d%d"  ,&m ,&n);
	for(int i = 1; i <= m; ++i){
		for(int j = 1; j <= n; ++j){
			scanf("%d" ,&a[i][j]);
		}
	}
	for(int i1 = 1; i1 <= m; ++i1){
		for(int j1 = 1; j1 <= n; ++j1){
			for(int i2 = 1; i2 <= m; ++i2){
				for(int j2 = 1; j2 <= n; ++j2){
					if(i1 == i2 && j1 == j2){//看到题解区里很多人处理重复的情况时也是向方格取数只加一遍,但个人认为既然班里每个同学都只会帮他们一次,就应该这么写
						continue;
					}
					dp[i1][j1][i2][j2] = max(dp[i1 - 1][j1][i2 - 1][j2], max(dp[i1][j1 - 1][i2 - 1][j2], max(dp[i1 - 1][j1][i2][j2 - 1], dp[i1][j1 - 1][i2][j2 - 1])));
					if(i1 != i2 && j1 != j2){
						dp[i1][j1][i2][j2] += a[i1][j1] + a[i2][j2];
					}
				}
			}
		}
	}
	printf("%d" ,dp[m - 1][n][m][n - 1]);//注意这里答案是这样的,由于我走重复时的判断,最后 dp[m][n][m][n] 的值为 0 ,但其实由于 a[m][n] 一定为零,所以走到 (m, n) 上一格或是左一格的收益是与走到 (m, n) 是一样的
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值