数塔(DP练习)

来源:互联网 发布:控制女高中生的软件 编辑:程序博客网 时间:2024/06/03 05:08

在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
这里写图片描述

有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

已经告诉你了,这是个DP的题目,你能AC吗?
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30
分析:DP,两种实现方式
1.自下而上

#include <cstdio>#include <cstring>#include <iostream>#include <cmath>#include <algorithm>#include <sstream>#include <string>#include <set>using namespace std;#define mem(a,n) memset(a,n,sizeof(a))typedef long long LL;const int mod=1e9+7;const int INF=0x3f3f3f3f;const int N=1e2+5;int a[N][N],dp[N][N];int main(){    int T,n;    scanf("%d",&T);    while(T--)    {        scanf("%d",&n);        for(int i=1;i<=n;i++)            for(int j=1;j<=i;j++)            scanf("%d",&dp[i][j]);        for(int i=n;i>1;i--)            for(int j=1;j<=i-1;j++)            dp[i-1][j]+=max(dp[i][j],dp[i][j+1]);        printf("%d\n",dp[1][1]);///因为第一层只有一个元素,最大的就是dp[1][1]    }    return 0;}

2.自上至下

#include <cstdio>#include <cstring>#include <iostream>#include <cmath>#include <algorithm>#include <sstream>#include <string>#include <set>using namespace std;#define mem(a,n) memset(a,n,sizeof(a))typedef long long LL;const int mod=1e9+7;const int INF=0x3f3f3f3f;const int N=1e2+5;int a[N][N],dp[N][N];int main(){    int T,n;    scanf("%d",&T);    while(T--)    {        scanf("%d",&n);        mem(dp,0);        for(int i=1; i<=n; i++)            for(int j=1; j<=i; j++)                scanf("%d",&a[i][j]);        int ans=0;        for(int i=1; i<=n; i++)            for(int j=1; j<=i; j++)            {                dp[i][j]+=a[i][j]+max(dp[i-1][j],dp[i-1][j-1]);                ans=max(dp[i][j],ans);///因为不知道最后一层最大的那个元素具体是哪一个,所以用ans记录            }        printf("%d\n",ans);    }    return 0;}

B - 数塔 Medium

Time limit2000 msMemory limit262144 kB

Summer is coming! It’s time for Iahub and Iahubina to work out, as they both want to look hot at the beach. The gym where they go is a matrix a with n lines and m columns. Let number a[i][j] represents the calories burned by performing workout at the cell of gym in the i-th line and the j-th column.

Iahub starts with workout located at line 1 and column 1. He needs to finish with workout a[n][m]. After finishing workout a[i][j], he can go to workout a[i + 1][j] or a[i][j + 1]. Similarly, Iahubina starts with workout a[n][1] and she needs to finish with workout a[1][m]. After finishing workout from cell a[i][j], she goes to either a[i][j + 1] or a[i - 1][j].

There is one additional condition for their training. They have to meet in exactly one cell of gym. At that cell, none of them will work out. They will talk about fast exponentiation (pretty odd small talk) and then both of them will move to the next workout.

If a workout was done by either Iahub or Iahubina, it counts as total gain. Please plan a workout for Iahub and Iahubina such as total gain to be as big as possible. Note, that Iahub and Iahubina can perform workouts with different speed, so the number of cells that they use to reach meet cell may differs.

Input
The first line of the input contains two integers n and m (3 ≤ n, m ≤ 1000). Each of the next n lines contains m integers: j-th number from i-th line denotes element a[i][j] (0 ≤ a[i][j] ≤ 105).

Output
The output contains a single number — the maximum total gain possible.

Example
Input
3 3
100 100 100
100 1 100
100 100 100
Output
800
Note
Iahub will choose exercises a[1][1] → a[1][2] → a[2][2] → a[3][2] → a[3][3]. Iahubina will choose exercises a[3][1] → a[2][1] → a[2][2] → a[2][3] → a[1][3].

题意:一个n*m的矩阵,A需要从左上角走到右下角(只能向下或向右走),B需要从左下角走到右上角(只能向上或向右走),经过的所有的格子都有一个对应的值,问两人最大收获是多少(两人相遇的那个格子不算入收获)。

分析:因为两个人在每个格子都有两种走法,要求最大值且只能在一个格子相遇
要保证只有一个格子重合,那么只可能是以下两种情况:
1) A向右走,相遇后继续向右走,而B向上走,相遇后继续向上走
2) A向下走,相遇后继续向下走,而B向右走,相遇后继续向右走
不能在矩阵的边界相遇

#include <cstdio>#include <cstring>#include <iostream>#include <cmath>#include <algorithm>#include <sstream>#include <string>#include <set>using namespace std;#define mem(a,n) memset(a,n,sizeof(a))typedef long long LL;const int mod=1e9+7;const int INF=0x3f3f3f3f;const int N=1e3+5;int a[N][N],dp1[N][N],dp2[N][N],dp3[N][N],dp4[N][N];void solve(int n,int m){    mem(dp1,0);    mem(dp2,0);    mem(dp3,0);    mem(dp4,0);    for(int i=1; i<=n; i++)  ///从左上角向右下角走        for(int j=1; j<=m; j++)            dp1[i][j]+=a[i][j]+max(dp1[i][j-1],dp1[i-1][j]);    for(int i=n; i>0; i--)  ///从右下角向左上角走        for(int j=m; j>0; j--)            dp2[i][j]+=a[i][j]+max(dp2[i][j+1],dp2[i+1][j]);    for(int i=n; i>0; i--)  ///从左下角向右上角走        for(int j=1; j<=m; j++)            dp3[i][j]+=a[i][j]+max(dp3[i][j-1],dp3[i+1][j]);    for(int i=1; i<=n; i++) ///从右上角向左下角走        for(int j=m; j>0; j--)            dp4[i][j]+=a[i][j]+max(dp4[i][j+1],dp4[i-1][j]);    int ans=0;    for(int i=2; i<n; i++)///枚举相遇的那个格子的行        for(int j=2; j<m; j++)///枚举列    不能在边界相遇        {            ans=max(ans,dp1[i-1][j]+dp2[i+1][j]+dp3[i][j-1]+dp4[i][j+1]);//一一对应            ans=max(ans,dp1[i][j-1]+dp2[i][j+1]+dp3[i+1][j]+dp4[i-1][j]);        }    printf("%d\n",ans);}int main(){    int n,m;    while(~scanf("%d%d",&n,&m))    {        for(int i=1; i<=n; i++)            for(int j=1; j<=m; j++)                scanf("%d",&a[i][j]);        solve(n,m);    }    return 0;}

C - 数塔 Hard fzu 2234 牧场物语
小茗同学正在玩牧场物语。该游戏的地图可看成一个边长为n的正方形。

小茗同学突然心血来潮要去砍树,然而,斧头在小茗的右下方。
这里写图片描述
小茗是个讲究效率的人,所以他会以最短路程走到右下角,然后再返回到左上角。并且在路上都会捡到/踩到一些物品,比如说花朵,钱和大便等。
这里写图片描述
物品只能被取最多一次。位于某个格子时,如果格子上还有物品,就一定要取走。起点和终点上也可能有物品。

每种物品我们将为其定义一个价值,当然往返之后我们取得的物品的价值和越大越好。但是小茗同学正在认真地玩游戏,请你计算出最大的价值和。

Input
多组数据(<=10),处理到EOF。

第一行输入正整数N(N≤100),表示正方形的大小。

接下来共N行,每行N个整数Ai,j(|Ai,j|≤10^9),表示相应对应位置上物品的价值。值为0表示没有物品。

Output
每组数据输出一个整数,表示最大价值和。

Sample Input
2
11 14
16 12
Sample Output
53

思路:题目中已经说的比较清楚的,先从左上角走到右下角再返回,每个位置上有物品(有相应的价值),求最大价值和
很容易的想到直接先从左上到右下走一遍,然后从右下返回,走过的地方做标记,两者相加求最大和,但是过了样例,却不能AC
这是别人用的记忆化搜索写的:http://blog.csdn.net/qq1059752567/article/details/62884319

然后看了别人的思路,大多都是转换成两个人从左上往右下走

以下思路引自: http://www.itkeyword.com/doc/8824376130833311341/acm
解题思路:小茗从左上角走到右下角再走回左上角,这个问题可以转换为两个人同时从左上角到右下角,使得路上取得最大权值
显然,此题就变成了一道DP题
令ans[k][(x1,y1)][(x2,y2)]表示1号小茗第k步走到点(x1,y1),同时2号小茗第k步走到(x2,y2),两人取得的权值之和
很容易的,我们可以得到转移方程
ans[k][(x1,y1)][(x2,y2)]=max{
ans[k-1][(x1-1,y1)][(x2-1,y2)],
ans[k-1][(x1,y1-1)][(x2,y2-1)],
ans[k-1][(x1-1,y1)][(x2,y2-1)],
ans[k-1][(x1,y1-1)][(x2-1,y2)]
}
另外,题目说小茗会以最短路程走到右下角,那么,说明小茗从起点到终点需要k=n+n-2步
而k与坐标点(x,y)的关系为k+2=x+y,因为0步的时候小茗位于(0,0)
那么我们就可以将转移方程简化掉两维,用步数和横坐标来替换纵坐标,如此,转移方程为
ans[k][x1][x2]=max{
ans[k-1][x1-1][x2-1],
ans[k-1][x1][x2],
ans[k-1][x1-1][x2],
ans[k-1][x1][x2-1]
}
步数增加之后,即便横坐标不变,纵坐标必定是增加了的
此题另一个优化点是步数k
因为k状态只和k-1状态有关,我们可以利用滚动数组来进一步优化空间

用滚动数组节约空间

#include <cstdio>#include <cstring>#include <iostream>#include <cmath>#include <algorithm>#include <sstream>#include <string>#include <set>using namespace std;#define mem(a,n) memset(a,n,sizeof(a))typedef long long LL;const LL mod=1e9+7;const double eps=1e-6;const LL INF=0x3f3f3f3f;const LL N=105;LL ans[2][N][N],a[N][N];///结果大于int的最大值int main(){    int n;    while(~scanf("%d",&n))    {        for(int i=1;i<=n;i++)            for(int j=1;j<=n;j++)            scanf("%I64d",&a[i][j]);        mem(ans,-INF);///初始化        int flag=0;        ans[0][1][1]=a[1][1];        for(int i=1;i<2*n-1;i++)//枚举步数        {            flag^=1;            for(int j=1;j<=n;j++)            {                for(int k=1;k<=n;k++)                {                    int tmp=1^flag;                    ans[flag][j][k]=max(max(ans[tmp][j-1][k],ans[tmp][j][k-1]),max(ans[tmp][j][k],ans[tmp][j-1][k-1]));                    if(j==k) ans[flag][j][k]+=a[j][i+2-j];///若返回时又走了一次相同的格子                    else ans[flag][j][k]+=a[j][i+2-j]+a[k][i+2-k];                }            }        }        printf("%I64d\n",ans[flag][n][n]);///注意用printf输出必须用I64d,这和fzu的OS有有关,或者直接用cout    }    return 0;}
原创粉丝点击