动态规划3_数塔问题

本文介绍了动态规划的经典问题——数塔问题,解析了题意和样例,阐述了三种不同的解题思路:从倒数第二层开始选取最大值、从第二层开始向下走以及记忆化搜索。并通过状态转移方程展示了如何实现这些策略。同时,给出了类似问题——免费的烧饼,分析了其解题方法并提供了状态转移方程。

通过动态规划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层开始向下走。从第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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值