前言
其实平面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;
}
但是我们再想想,如果数据再大一点怎么办呢?请听下回分解~~~~