HDUOJ1421(DP)

ACM1421
又是动态规划的题目,dp方面问题对现阶段的自己还是一个坎,还是要多理解多刷题

数塔
首先从简单的数塔入手,从第一行开始走,走到最后一行,怎么使经过的值之和最大?

dp问题的核心:找到状态并建立状态转移方程

首先需要建立一个数塔模型,模型中的元素用a(i,j)(j<=i)表示,如第三行第二个元素10用a(3,2)表示。并且我们规定从a(i,j)往下走所能得到的最大值为dp(i,j)
我们以数塔中任意一个元素a(i,j)为一个状态,根据规则,该元素可以选择向左下方a(i+1,j)或者右下方a(i+1,j+1)移动(最后一行除外),显然对于处于a(i,j)一定选择{dp(i+1,j),dp(i+1,j+1)}中较大的值,于是我们得到了dp(i,j)的计算公式:dp(i,j)=a(i,j)+max(dp(i+1,j),dp(i+1,j+1)),这就是我们所要找的状态转移方程。很容易就知道该问题的最终答案是dp(1,1)

接下来介绍三种方法来求解:
1.递归求解

int dp(int i,int j)
{
    if(i!=n)return a[i][j]+=max(dp(i+1,j),dp(i+1,j+1));
    else return 0;
    //n表示最后一行的行数,即为边界
}

分析该算法,会发现效率很低。例如对于上图dp(3,2),dp(4,2),dp(4,3)将会被重复计算,但是这部分的结果一样,却被重复计算,我们就要设法使这部分不被重复,很显然,要使他不重复,那就在第一次计算的时候把结果记录下来,下次遇到的时候直接使用,就避免了重复计算。

2.根据算法1的分析,我们得到了带记忆功能的算法

int dp(int i,int j)
{
//第一行就是所说的记忆功能,如果dp>=0,就表明它已经被计算过了,可以直接使用,
//所以在函数之前要将dp的值全赋值成-1,表示dp都未被计算过memset(dp,-1,sizeof(dp))
    if(dp(i,j)>=0)return dp(i,j);
    else return dp[i][j]=a[i][j]+max(dp(i+1,j),dp(i+1,j+1));
}

这里写图片描述这里写图片描述

从图中很直观的看出记忆功能的算法能直接利用计算过的结果。

3.递推算法
算法3改变了思路,前两种都是自顶向下计算,算法3则选择从下向上计算,而且规定一下是从左下角向顶角计算。

for(int i=n;i>=1;i--)
    for(int j=1;j<=i;j++)
        dp[i][j]=a[i][j]+max(a[i-1][j],a[i-1][j+1]);
//仔细分析甚至连dp数组都可以不用
        a[i][j]+=max(a[i-1][j],a[i-1][j+1]);

下面给出数塔问题ACM2084完整代码,这里选择算法3

#include<iostream>
#include<algorithm>
#define MAXN 105
int a[MAXN][MAXN];
using namespace std;
int main()
{
    int t;
    cin >> t;
    while (t--) {
        int n; cin >> n;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= i; j++)
                cin >> a[i][j];
        for (int i = n - 1; i >= 1; i--)
            for (int j = 1; j <= i; j++)
                a[i][j] += max(a[i + 1][j], a[i + 1][j + 1]);
        cout << a[1][1] << endl;
    }
    return 0;
}

接下来分析一下开头的ACM1421
首先必须承认渣渣的我这道题想了很久,也是借鉴了别人的思路。

先申明用a[n]来存储输入的数据,用dp[i][j]表示从i个数中取j对时的最小值,例如dp[3][1]表示从3个数取1对的最小疲劳度(当然三个数的时候也只能取一对)

我想从题意中应该很容易想到给数组a进行排序

sort(a,a+n);

我们先从简单的找找规律:
当只有两个数的时候,结果显然等于(a[2]-a[1])*(a[2]-a[1]),根据前面的定义我们把结果记作dp[2][1];
当有三个数,要么a[1]和a[2],要么a[2]和a[3]组合,我们取两个组合的最小值,因此:

dp[3][1]=min(dp[2][1],(a[3]-a[2])*(a[3]-a[2]));

我们接着在推一个当四个数的时候,此时我们可以选取一对或者两对,
此时选取一对可以分成两类:

1.不包括第四个数时,dp[4][1]=dp[3][1];
2.包括第四个数时,dp[4][1]=(a[4]-a[3])*(a[4]-a[3]);

选取两对:

dp[4][2]=dp[2][1]+(a[4]-a[3])*(a[4]-a[3]);

不知道你们发现什么没(如果还没有,可以在推一两组找找规律)

dp[i][j]=min(dp[i-1][j],dp[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]);
//其实这就是我们要找的状态转移方程
//假设要找i个元素中j对的最小值:要么从前i-1个元素中找j对;要么从前i-2个元素中找j-1对+a[i]-a[i-1])*(a[i]-a[i-1];

既然找到了状态转移方程,我们就根据题意进行一些细节的处理

#include<iostream>
#include<algorithm>
#define MAXN 2050
int dp[MAXN][MAXN];
int a[MAXN];
using namespace std;
int main()
{
    int n, k;
    while (cin >> n >> k)
    {

        for (int i = 1; i <=n; i++)
            cin >> a[i];
        sort(a+1, a + n+1);
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= k; j++)
                dp[i][j] = 268435456; 
        for (int i = 2; i <= n; i++)
            for (int j = 1; 2*j<= i ; j++)
                dp[i][j] = min(dp[i - 2][j - 1] + (a[i] - a[i - 1])*(a[i] - a[i - 1]), dp[i - 1][j]);
        cout << dp[n][k]<<endl;
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值