nyoj 61:点击打开链接
找两条从左上角到右下角不相交的路径(除了头和尾),使路径上的和最大.
首先是四维的数组,比较好理解。
f[x1][y1][x2][y2]=max{f[x1-1][y1][x2-1][y2],f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2-1][y2],f[x1][y1-1][x2][y2-1]}+map[x1][y1]+map[x2][y2] ;(其中x1 !=x2 || y1 !=y2)
其中f[x1][y1][x2][y2]代表当前第一条路径经过点(x1, y1),第二条路径经过点(x2, y2),注意这两点不能重合。
每个路径都能从左边和上面走过来,所以在四种情况中选一个最大的。但对于这一题来说会超时。
进一步的将四位数组改成三维。
因为每个时刻x1 + y1 == x2 + y2,也就是所走的步数都是一样的,所以,就可以将y1, y2去掉然后在增添一个k值表示所走的横纵坐标之和。
f[k][x1][x2] 这样y1 = k - x1, y2 = k - x2,状态转移方程为max(f[k - 1][x1 - 1][x2], f[k - 1][x1 - 1][x2 - 1], f[k - 1][x1][x2], f[k - 1][x1][x2 - 1]) + map[x1][k - x1] + map[x2][k - x2];
知道了状态转移方程但是写起来还是有很多需要注意的地方……
注意solve()中第三个for循环,jshi从i+1开始的。j从i+1开始可以避免j==i,也可以保证第一条路径一定选取在第二条路径的上方,如果j也从1开始循环,那么会有重复的部分,因为第一条路径在第二条路径的上方和第一条路径在第二条路径的下方其实是重复的,总的来说两种情况所得的结果是一样的。
#include <stdio.h>
#include <string.h>
int a[100][100];
int dp[110][110][110];
int m, n;
inline int max(int c, int d, int e, int f)
{
int m = c;
if(m < d)
m = d;
if(m < e)
m = e;
if(m < f)
m = f;
return m;
}
int solve()
{
int i, j, k, c, d;
for(k = 2; k < m + n; k ++)
{//枚举所走的横纵坐标之和
c = m > k ? k : m;//横坐标不可能大于横纵坐标之和,也不可能大于横坐标的边界
for(i = 1; i <= c; i++)
{//枚举第一条路径的横坐标
for(j = i + 1; j <= c; j++)
{//第二条路径的横坐标
dp[k][i][j] = max(dp[k - 1][i][j], dp[k - 1][i - 1][j], dp[k - 1][i][j - 1], dp[k - 1][i - 1][j - 1]);
dp[k][i][j] += a[i][k - i] + a[j][k - j];
}
}
}
//注意返回的不是dp[m + n][m][m],也不是dp[m +n -1][m][m - 1],因为dp[m +n][m][m]根本走不到
//i的值一定比j小,所以dp[m +n- 1][m][m - 1]的值是0
return dp[m + n - 1][m - 1][m];
}
int main (void)
{
int t;
scanf("%d", &t);
while(t --)
{
memset(dp, 0, sizeof(dp));
int i, j;
scanf("%d %d", &m, &n);
for(i = 1; i <= m; i++)
for(j = 1; j <= n; j++)
scanf("%d", &a[i][j]);
//如果map[m][n]也是有非0数值的,就要在单独加上
printf("%d\n", solve());
}
return 0;
}