P1004 [NOIP2000 提高组] 方格取数
显然,对于这道题,我们一开始能想到的解法肯定是跑两遍二维 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]=max⎩⎪⎪⎪⎨⎪⎪⎪⎧dp[i1−1][j1][i2−1][j2]dp[i1][j1−1][i2−1][j2]dp[i1−1][j1][i2][j2−1]dp[i1][j1−1][i2][j2−1]+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 提高组] 传纸条
这道题与方格取数题面上的不同是方格取数是从左上到右下走两遍而传纸条是左上到右下走一遍再从右下到左上走一遍,但其实本质是一样的
然而如果我们左上到右下走一遍再从右下到左上走一遍,就没有办法判断走过的格了,同一个方向走那就可以要求不允许同时到达某个点,以此来解决不能重复走的问题.从两个方向走只能保证中间那个位置不重复,前后是没有办法判重的,所以这道题状态转移方程与方格取数相同
注意最后的答案是 d p [ m − 1 ] [ n ] [ m ] [ n − 1 ] dp[m - 1][n][m][n - 1] dp[m−1][n][m][n−1] ,注释会详细说
#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;
}