Problem H: 方格取数
Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 28 Solved: 9
Description
有N * N个格子,每个格子里有正数或者0,从最左上角往最右下角走,只能向下和向右,一共走两次(即从左上角走到右下角走两趟),把所有经过的格子的数加起来,求最大值SUM,且两次如果经过同一个格子,则最后总和SUM中该格子的计数只加一次。
Input
第一行N(1 <= N <= 100)
然后N行,每行N个数。依次表示N * N每个格子的数值。(0 <= value <= 1000) ;
Output
输出最大值SUM。
Sample Input
Sample Output
HINT
Hint
Possible Way:
第一次 1 -> 2 -> 2 -> 4 -> 2
第二次 1 -> 2 -> 3 -> 1 -> 2
SUM = 15
思路:
DP。一共走两次,可以看成两个人同时从起点走向终点。每个点都可以从上面或者左边得到,所以两个人同时走的话,可以有四种组合。故可以得到转移方程:
Dp [ X1 ] [ Y1 ] [ X2 ] [ Y2 ] = max (Dp [ X1 - 1 ] [ Y1 ] [ X2 - 1 ] [ Y2 ] ,
Dp [ X1 - 1 ] [ Y1 ] [ X2 ] [ Y2 - 1 ] ,
Dp [ X1 ] [ Y1 - 1 ] [ X2 - 1 ] [ Y2 ] ,
Dp [ X1 ] [ Y1 - 1 ] [ X2 ] [ Y2 - 1 ] )+ Map [ X1 ] [ Y1 ] + Map [ X2 ] [ Y2 ];
四个循环,范围为 1 ~ 100,那么时间复杂度 O(n ^ 4)会造成 TLE,所以还要进一步优化。用 k 代表步数,那么转移方程可以变为:
Dp [ k ] [ X1 ] [ X2 ] = max (Dp [ k - 1 ] [ X1 - 1 ] [ X2 - 1 ] ,
Dp [ k - 1 ] [ X1 - 1 ] [ X2 ] ,
Dp [ k - 1 ] [ X1 ] [ X2 - 1 ] ,
Dp [ k - 1 ] [ X1 ] [ X2 ] ) + Map [ X1 ] [ k - X1 ] + Map [ X2 ] [ k - X2 ];
这样的话时间复杂度降为 O (n ^ 3 )就不会导致TLE了。
如何避免走相同的格的时候只加一次呢?
因为 k 是一样的,说明当 x1 == x2 的时候,y1 == y2,这时候两个走在了同一格,所以只要判断是否 x1 == x2 就能决定要不要加两次了。若题目要求改为不允许走过同一点,将 x1 == x2 时候 continue 就可以了。
AC:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[210][105][105];
int Map[105][105];
int main () {
int n, sum;
scanf("%d", &n);
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
scanf("%d", &Map[i][j]);
sum = n + n - 2;
for (int k = 0; k <= sum; ++k) {
for (int x1 = 0; x1 < n; ++x1) {
for (int x2 = 0; x2 < n; ++x2) {
int ans = 0;
int y1 = k - x1, y2 = k - x2;
if (k > 0 && x1 > 0 && x2 > 0)
ans = max(ans, dp[k - 1][x1 - 1][x2 - 1]);
if (k > 0 && x1 > 0 && y2 > 0)
ans = max(ans, dp[k - 1][x1 - 1][x2]);
if (k > 0 && y1 > 0 && x2 > 0)
ans = max(ans, dp[k - 1][x1][x2 - 1]);
if (k > 0 && y1 > 0 && y2 > 0)
ans = max(ans, dp[k - 1][x1][x2]);
if (y1 < 0 || y2 < 0) continue;
dp[k][x1][x2] = ans + Map[x1][y1] +
(x1 == x2 ? 0 : Map[x2][y2]);
}
}
}
printf("%d\n", dp[sum][n - 1][n - 1]);
return 0;
}