ACM_JLINE.

传纸条

字数统计: 1.2k阅读时长: 6 min
2019/03/04 Share

传纸条 Day 1

原题链接:传送门

题目大意

在 m * n 的矩阵中, 从 (1,1) 到 (m, n) 的两条路劲和最大, 并且不相交

解法一

其实是一个很简单的棋盘形dp,可以直接看成 (1,1) 到 (m, n) 的两条严格不相交路劲。

直接思维DP,设f[i][j][k][l]为从第一张纸条到达 (i, j) ,第二张纸条 (k,l) 的路径上取得的最大的好心程度和。

那么特别显然,转移方程是 f[i][j][k][l]=max( f[i][j-1][k-1][l] , f[i-1][j][k][l-1] , f[i][j-1][k][l-1] , f[i-1][j][k-1][l] )+a[i][j]+a[k][l]。

要小心l的枚举范围,应该是从j+1到m,只有这样,在枚举第二条路的时候可以控制下标的l不会和j有相等的可能,这样可以保证两条路一定不相交

由于终点的值是0,所以目标状态就是f[n][m-1][n-1][m]。

如果你不想这样做,那就让l直接从1枚举,但需要加一个判断,判断当前的(i,j)和(k,l)是不是重合了,如果重合那就把f数组对应的这个地方在转移后减掉一个a[i][j]或者a[k][l]。

时间复杂度就是 O(m * m * n * n)

Code block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 55;
int f[N][N][N][N];
int a[N][N];
int n, m;

int Max(int a, int b, int c, int d){
if(a < b) a = b;
if(a < c) a = c;
if(a < d) a = d;
return a;
}

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 i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
for(int l = 1; l <= m; l++){
for(int r = j + 1; r <= n; r++){
f[i][j][l][r] = Max(f[i-1][j][l-1][r],f[i][j-1][l][r-1],f[i][j-1][l-1][r],f[i-1][j][l][r-1]);
f[i][j][l][r] += a[i][j] + a[l][r];
}
}
}
}

printf("%d\n", f[m][n-1][m-1][n]);

return 0;
}

解法二

简化到三维,因为 i + j == l + k == Step

那么可以用f[k][i][j]记录最大值

转移式 f[k][i][j] = Max(f[k-1][i-1][j], f[k-1][i-1][j-1], f[k-1][i][j], f[k-1][i][j-1])

目标 f[n+m-1][m][m]

第一维度维护的是在传的过程中纵坐标与横坐标的和。

第二维度维护的是第一张纸条的点的纵坐标。

第三维度维护的是第二张纸条的点的纵坐标。

让第一张纸相对于第二张靠右。

时间复杂度 O(m * n * (m + n) )

Code block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 55;
int f[N*2][N][N]; //第一维开两倍,一不小心RE了
int a[N][N];
int n, m;

int Max(int a, int b, int c, int d){
if(a < b) a = b;
if(a < c) a = c;
if(a < d) a = d;
return a;
}

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 k = 1; k < n + m; k++){
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++){
if(k < i || k < j) continue;
f[k][i][j] = Max(f[k-1][i-1][j], f[k-1][i-1][j-1], f[k-1][i][j], f[k-1][i][j-1]);
f[k][i][j] += a[i][k-i+1] + a[j][k-j+1];
if(i == j) f[k][i][j] -= a[i][k-i+1];
}
}
}


printf("%d\n", f[n+m-1][m][m]);

return 0;
}

解法三

之前在 POJ 上写过一题会 MTL 的, 可以优化第一维, 因为第一维只会从他前面一个状态
转移,即 f[k] 只会从 f[k-1] 来,那么第一维只要开个 2 就行了

传送门POJ // 有空补一下题解, 蛮有意思的环形处理

Code block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 55;
int f[2][N][N];
int a[N][N];
int n, m;

int Max(int a, int b, int c, int d){
if(a < b) a = b;
if(a < c) a = c;
if(a < d) a = d;
return a;
}

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 k = 1; k < n + m; k++){
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++){
if(k < i || k < j) continue;
f[k&1][i][j] = Max(f[(k-1)&1][i-1][j], f[(k-1)&1][i-1][j-1], f[(k-1)&1][i][j], f[(k-1)&1][i][j-1]);
f[k&1][i][j] += a[i][k-i+1] + a[j][k-j+1];
if(i == j) f[k&1][i][j] -= a[i][k-i+1];
}
}
}


printf("%d\n", f[(n+m-1)&1][m][m]);

return 0;
}
CATALOG
  1. 1. 传纸条 Day 1
    1. 1.1. 原题链接:传送门
      1. 1.1.1. 题目大意:
      2. 1.1.2. 解法一:
      3. 1.1.3. Code block
      4. 1.1.4. 解法二:
      5. 1.1.5. Code block
      6. 1.1.6. 解法三:
      7. 1.1.7. Code block