详解平面DP(上)

前言

其实平面DP和正常的dp没有什么本质上的区别,只不过是在二维的面上进行DP,而且,客观的说,其实和递推没有什么区别,不要把他想的太难了

讲解

本蒻鸡思前想后,好像关于平面DP的理论知识好像没有什么,所以我们直接上题,从题来入手

[NOIP2000 提高组] 方格取数

题目传送门

题目背景

NOIP 2000 提高组 T4

题目描述

设有 N × N N \times N N×N 的方格图 ( N ≤ 9 ) (N \le 9) (N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0 0 0。如下图所示(见样例):

某人从图的左上角的 A A A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B B B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0 0 0)。

此人从 A A A 点到 B B B 点共走两次,试找出 2 2 2 条这样的路径,使得取得的数之和为最大。

输入格式

输入的第一行为一个整数 N N N(表示 N × N N \times N N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 0 0 表示输入结束。

输出格式

只需输出一个整数,表示 2 2 2 条路径上取得的最大的和。

样例 #1

样例输入 #1
8
2 3 13
2 6  6
3 5  7
4 4 14
5 2 21
5 6  4
6 3 15
7 2 14
0 0  0
样例输出 #1
67

提示

数据范围: 1 ≤ N ≤ 9 1\le N\le 9 1≤N≤9。

思路

如果该题只取一次数或者取走一次之后原来的数还在,就是一道简单的递推的题,但是该题需要来回取两次,如果我们按照贪心+递推的思想,取完一次之后修改方格中的数,然后再取一次,那么很容易举出反例,所以我们要思考其他办法。

我们要知道,无论是从A-->B还是从B-->A,对于取数的答案是不会有影响的,我们不妨看做从A-->B取数取两次,我们让这两次取数同时进行。

如果要同时表示表示这种状态就需要开四维数组dp[i][j][k][l],表示分别走到点(i,j)和(k,l)的最优解。

这种的思路还是比较好想的,代码如下

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int mp[10][10],dp[10][10][10][10];
int main(){
	int n;
	cin>>n;
	while (1){
		int a,b,c;
		cin>>a>>b>>c;
		if (a==0&&b==0&&c==0)break;
		mp[a][b]=c;
	}
	for (int i=1;i<=n;i++){
		for (int j=1;j<=n;j++){
			for (int l=1;l<=n;l++){
				for (int k=1;k<=n;k++){
					dp[i][j][l][k]=max(max(dp[i-1][j][l-1][k],dp[i-1][j][l][k-1]),max(dp[i][j-1][l-1][k],dp[i][j-1][l][k-1]))+mp[i][j]+mp[l][k];
					if (i==l&&j==k)dp[i][j][l][k]-=mp[i][j];//如果相同则要减去一个
				}
			}
		}
	}
	cout<<dp[n][n][n][n];
	return 0;
}

因为这是2000年的题,那是的信息竞赛的水平不高,所以范围只有9,但是我们要有科学家钻研的品格(我们老师的梗),所以我们来探寻一下O(n^3^)的方法

不难想到我们可以发现,每当我们走一步,那么x坐标和y坐标之间总会有一个数加1所以,我们可以用k来表示x坐标和y坐标的和,从而通过y坐标来计算出x坐标。由于k对于两条同时处理的路径可以是公共的,所以我们就可以用 f [ k ] [ y 1 ] [ y 2 ] f[k][y1][y2] f[k][y1][y2]来表示当前状态。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int mp[700][700],dp[700][700][700];
int main(){
	int n;
	cin>>n;
	while (1){
		int a,b,c;
		cin>>a>>b>>c;
		if (a==0&&b==0&&c==0)break;
		mp[a][b]=c;
	}
	for (int i=1;i<=n+n;i++){
		for (int l=1;l<=min(i,n);l++){
			for (int k=1;k<=min(i,n);k++){
				dp[i][l][k]=max(max(dp[i-1][l-1][k],dp[i-1][l][k]),max(dp[i-1][l][k-1],dp[i-1][l-1][k-1]))+mp[i-l][l]+mp[i-k][k];
				if (l==k)dp[i][l][k]-=mp[i-l][l];
			}
		}
	}
	cout<<dp[n+n][n][n];
	return 0;
}

[NOIP2008 提高组] 传纸条

题目传送门

题目描述

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排坐成一个 m m m 行 n n n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 ( 1 , 1 ) (1,1) (1,1),小轩坐在矩阵的右下角,坐标 ( m , n ) (m,n) (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 0 0 表示),可以用一个 [ 0 , 100 ] [0,100] [0,100] 内的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有两个用空格隔开的整数 m m m 和 n n n,表示班里有 m m m 行 n n n 列。

接下来的 m m m 行是一个 m × n m \times n m×n 的矩阵,矩阵中第 i i i 行 j j j 列的整数表示坐在第 i i i 行 j j j 列的学生的好心程度。每行的 n n n 个整数之间用空格隔开。

输出格式

输出文件共一行一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

样例 #1

样例输入 #1

3 3
0 3 9
2 8 5
5 7 0

样例输出 #1

34

提示

【数据范围】

对于 30 % 30\% 30% 的数据,满足 1 ≤ m , n ≤ 10 1 \le m,n \le 10 1≤m,n≤10。

对于 100 % 100\% 100% 的数据,满足 1 ≤ m , n ≤ 50 1 \le m,n \le 50 1≤m,n≤50。

【题目来源】

NOIP 2008 提高组第三题。

思路

这道题其实和上一道题差不多,所以我们直接写代码,值得注意的是,这里的每一个人都是正数,所以不用考虑走了一次后就不能走了的情况,基本上可以完全参照上一道题的写法

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

long long mp[120][120],dp[120][120][120];
int main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			cin>>mp[i][j];
		}
	}
	for (int i=2;i<=n+m-1;i++){
		for (int l=1;l<=min(i,n);l++){
			for (int k=1;k<=min(i,n);k++){
				dp[i][l][k]=max(max(dp[i-1][l-1][k],dp[i-1][l][k]),max(dp[i-1][l][k-1],dp[i-1][l-1][k-1]))+mp[l][i-l+1]+mp[k][i-k+1];
				if (l==k)dp[i][l][k]-=mp[l][i-l+1];
			}
		}
	}
	cout<<dp[n+m-1][n][n];
	return 0;
}

但是话又说回来,如果这道题是有负数的呢?那么我们就要考虑如何排除又重复的情况了。其实解题思路是一样的,只是状态转移方程不同,既然两条路径不能重叠,那么一定有一条路径在上上,一条路径在下方,这里我们始终让i<j就行了,但是注意特判起点和终点。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

long long mp[220][220],dp[420][220][220];
int main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			cin>>mp[i][j];
		}
	}
	dp[2][1][1]=mp[1][1];
	for (int i=2;i<=n+m;i++){
		for (int j=0;j<=n;j++){
			for (int k=0;k<=n;k++){
				dp[i][j][k]=INT_MIN;
			}
		}
	}
	dp[2][1][1]=mp[1][1];
	for (int i=2;i<=n+m;i++){
		for (int l=1;l<=n&&l<i;l++){
			for (int k=1;k<min(l,i);k++){
				dp[i][l][k]=max(max(dp[i-1][l-1][k],dp[i-1][l][k]),max(dp[i-1][l][k-1],dp[i-1][l-1][k-1]))+mp[l][i-l]+mp[k][i-k];
			}
		}
	}
	dp[n+m][n][n]=dp[n+m-1][n][n-1]+mp[n][m];
	cout<<dp[n+m][n][n];
	return 0;
}

最大加权矩形

题目传送门

题目描述

为了更好的备战 NOIP2013,电脑组的几个穿黑丝的女孩子 LYQ,ZSC,ZHQ 认为,我们不光需要机房,我们还需要运动,于是就决定找校长申请一块电脑组的课余运动场地,听说她们都是电脑组的高手,校长没有马上答应他们,而是先给她们出了一道数学题,并且告诉她们:你们能获得的运动场地的面积就是你们能找到的这个最大的数字。

校长先给他们一个 n × n n\times n n×n 矩阵。要求矩阵中最大加权矩形,即矩阵的每一个元素都有一权值,权值定义在整数集上。从中找一矩形,矩形大小无限制,是其中包含的所有元素的和最大 。矩阵的每个元素属于 [ − 127 , 127 ] [-127,127] [−127,127] ,例如

plain 复制代码
 0 --2 --7  0 
 9  2 --6  2
-4  1 --4  1 
-1  8  0 --2

在左下角:

plain 复制代码
9  2
-4  1
-1  8

和为 15 15 15。

几个女孩子有点犯难了,于是就找到了电脑组精打细算的 HZH,TZY 小朋友帮忙计算,但是遗憾的是他们的答案都不一样,涉及土地的事情我们可不能含糊,你能帮忙计算出校长所给的矩形中加权和最大的矩形吗?

输入格式

第一行: n n n,接下来是 n n n 行 n n n 列的矩阵。

输出格式

最大矩形(子矩阵)的和。

样例 #1

样例输入 #1

4
0 -2 -7 0
 9 2 -6 2
-4 1 -4  1 
-1 8  0 -2

样例输出 #1

15

提示

1 ≤ n ≤ 120 1 \leq n\le 120 1≤n≤120

首先可以看到这道题的数据范围似乎不是很大,好像O(n^4^)就可以过,那么我们就先这样去想想,是不是可以利用前缀和呢?答案是可以的,代码如下

cpp 复制代码
#include<iostream>
using namespace std;
int n,mx=INT_MIN;
int a[130][130],sum[130][130],qz[130][130];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
            qz[i][j]=qz[i][j-1]+a[i][j];
            sum[i][j]=qz[i][j]+sum[i-1][j];
        }
    }
    for(int x1=1;x1<=n;x1++){
    	for(int y1=1;y1<=n;y1++){
    		for(int x2=1;x2<=n;x2++){
    			for(int y2=1;y2<=n;y2++){
    				if (x2<x1||y2<y1)continue;
    				mx=max(mx,sum[x2][y2]+sum[x1-1][y1-1]-sum[x2][y1-1]-sum[x1-1][y2]);
    			}
    		}
    	}
    }
    cout<<mx;
	return 0;
}

但是我们再想想,如果数据再大一点怎么办呢?请听下回分解~~~~

![在这里插入图片描述](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https://cn.bing.com/images/search?view=detailV2\&ccid=w5Af3r7K\&id=4BDD3F5C94C39BC3D12D5CEA1528744455BBAC1D\&thid=OIP.w5Af3r7KPyEO56el6hRklgHaKd\&mediaurl=https%253a%252f%252fts1.cn.mm.bing.net%252fth%252fid%252fR-C.c3901fdebeca3f210ee7a7a5ea146496%253frik%253dHay7VUR0KBXqXA%2526riu%253dhttp%25253a%25252f%25252fcomic.people.com.cn%25252fmediafile%25252f201112%25252f26%25252![请添加图片描述](https://i-blog.csdnimg.cn/direct/5ccf357dfca6456599594b258d50a8ea.jpeg)![请添加图片描述](https://i-blog.csdnimg.cn/direct/639e1e8c66884fc98229a784ebf1d0a6.jpeg)

相关推荐
小刘|15 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
jjyangyou20 分钟前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
van叶~36 分钟前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
简简单单做算法37 分钟前
基于Retinex算法的图像去雾matlab仿真
算法·matlab·图像去雾·retinex
云卓SKYDROID1 小时前
除草机器人算法以及技术详解!
算法·机器人·科普·高科技·云卓科技·算法技术
半盏茶香1 小时前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
徐子童1 小时前
双指针算法习题解答
算法
想要打 Acm 的小周同学呀1 小时前
LRU缓存算法
java·算法·缓存
劲夫学编程2 小时前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪3 小时前
孤岛的总面积(Dfs C#
算法·深度优先