通过动态规划2大概知道动态规划如何去分析、思考问题。
这小节主要是去理解动态规划里经典的问题--数塔问题。
题目链接:HDU2084
题意:
从最高点走到最下面一层,每次可以走到相邻的节点,每一步经过一个数字,将其累加,求最大的累加值是多少。
样例:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
(1)
思路:从倒数第2层开始,选取下一层并且与其邻接的节点中的最大值,将下一层并且与其邻接的节点中的最大值加上当前的值之和替换当前的值。
表示最优解的走法如果经过当前的位置时,将会选择下一层并且与其邻接的节点中的最大值。使得最终的值最大。
例如样例中第4行第1个是2.与2邻接的有第5行的第一个值4和第二个值5.所以会选择5.将5+2=7替换2.代表如果最优解的走法经过第四行第一个2时,
将会走向下一层的5.使得最终最大。
样例的变化:
最终最大值为30.
状态转移方程式:
dp[i][j]=dp[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
代码实现:(46MS)
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int dp[110][110];
int main()
{
//freopen("in.txt","r",stdin);
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-1;i>=1;i--) //从倒数第2层开始选择
{
for(int j=1;j<=i;j++) //每行从左到右选择最大值,并且更新。
{
dp[i][j]=dp[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
}
}
printf("%d\n",dp[1][1]);
}
return 0;
}
类似于第一种思想。这次是从第2层开始向下走。从第2层选择第1层并且与其相邻的最大值。然后第3层选择第2层并且与其相邻的最大值。选择后,将
最大值加上原来的值之和替换原来的值。
例如样例中第2层的3和8都必须选择第一层的7.最终替换成10和15.
然后第3层的第一个8选择第2层的10.替换成18.以此类推。。。
最后从最下面一层选出最大值即可。
样例的变化:
状态转移方程:
dp[i][j]=dp[i][j]+max(dp[i-1][j],dp[i-1][j-1])
代码实现:(46MS)
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int dp[110][110];
int main()
{
//freopen("in.txt","r",stdin);
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=2;i<=n;i++) //从第2层开始
{
for(int j=1;j<=i;j++)//从左到右
{
dp[i][j]=dp[i][j]+max(dp[i-1][j],dp[i-1][j-1]);
}
}
//找出最后一行的最大值。
int Max=0;
for(int i=1;i<=n;i++)
Max=max(Max,dp[n][i]);
printf("%d\n",Max);
}
return 0;
}
(3)递归计算。
为了避免重复计算,边计算边记录。(记忆化搜索)
代码实现:(62MS)
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[110][110];
int dp[110][110];
int n;
int slove(int i,int j)
{
if(dp[i][j]>=0)
return dp[i][j];
return dp[i][j]=a[i][j]+( i==n+1?0:
max(slove(i+1,j),slove(i+1,j+1)));
}
int main()
{
//freopen("in.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
scanf("%d",&a[i][j]);
}
}
memset(dp,-1,sizeof(dp));
printf("%d\n",slove(1,1));
}
return 0;
}
类似的题目:免费的烧饼:HDU1176
题意:
输入一个数n,然后获取n个信息,每个信息包含x,T,表示T秒时有一个饼掉在x处,同一秒钟在同一点上可能掉下多个馅饼。
每次能移动一个格子,假如前一秒在i处,则下一秒的移动为i-1,i,i+1。求最大能接到多少个饼。
分析:
先除去无法接到的饼,例如第3秒在0处掉落,则原位置为5,在3秒内是走不到0处的。
然后其余的用a[i][j]表示第i秒第j处掉落的饼的数量,用dp[i][j]表示第i秒第j处最多接到饼的数量。
可以得到状态转移方程dp[i][j]=a[i][j]+max(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]);表示第i秒第j处最多接到饼的数量等于
第i秒第j处掉落的饼数量(即a[i][j])加上前一秒j-1,j,j+1三个位置最多拿到的饼的数量的最大值。因为前一秒在j-1,
j,j+1都可以移动到j处。
代码实现:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[110000][12];
int a[110000][12];
int main()
{
// freopen("in.txt","r",stdin);
int x,t,n;
while(scanf("%d",&n)!=EOF&&n)
{
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
int Maxtime=0;
for(int i=0;i<n;i++)
{
scanf("%d %d",&x,&t);//t秒掉在x处
if(abs(5-x)>t) //无法接到的饼
continue;
a[t][x]++;
Maxtime=max(Maxtime,t);
}
for(int i=1;i<=Maxtime;i++)
{
for(int j=0;j<=10;j++)
{
//边界处理
if(j==0)
dp[i][j]=a[i][j]+max(dp[i-1][j],dp[i-1][j+1]);
else
dp[i][j]=a[i][j]+max(dp[i-1][j],
max(dp[i-1][j-1],dp[i-1][j+1]));
}
}
int Max=0;
for(int i=0;i<=10;i++)
Max=max(Max,dp[Maxtime][i]);
printf("%d\n",Max);
}
return 0;
}