前言
其实平面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)的最优解。
这种的思路还是比较好想的,代码如下
#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(n3)的方法
不难想到我们可以发现,每当我们走一步,那么x坐标和y坐标之间总会有一个数加1所以,我们可以用k来表示x坐标和y坐标的和,从而通过y坐标来计算出x坐标。由于k对于两条同时处理的路径可以是公共的,所以我们就可以用 f [ k ] [ y 1 ] [ y 2 ] f[k][y1][y2] f[k][y1][y2]来表示当前状态。
#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 提高组第三题。
思路
这道题其实和上一道题差不多,所以我们直接写代码,值得注意的是,这里的每一个人都是正数,所以不用考虑走了一次后就不能走了的情况,基本上可以完全参照上一道题的写法
#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就行了,但是注意特判起点和终点。
#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] ,例如
0 –2 –7 0 9 2 –6 2
-4 1 –4 1
-1 8 0 –2
在左下角:
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 09 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(n4)就可以过,那么我们就先这样去想想,是不是可以利用前缀和呢?答案是可以的,代码如下
#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