HDOJ1421
又是动态规划的题目,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.递归求解
1 | int dp(int i,int j) |
分析该算法,会发现效率很低。例如对于上图dp(3,2),dp(4,2),dp(4,3)将会被重复计算,但是这部分的结果一样,却被重复计算,我们就要设法使这部分不被重复,很显然,要使他不重复,那就在第一次计算的时候把结果记录下来,下次遇到的时候直接使用,就避免了重复计算。
2.根据算法1的分析,我们得到了带记忆功能的算法。
1 | int dp(int i,int j) |
从图中很直观的看出记忆功能的算法能直接利用计算过的结果。
3.递推算法
算法3改变了思路,前两种都是自顶向下计算,算法3则选择从下向上计算,而且规定一下是从左下角向顶角计算。1
2
3
4
5for(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
1 | #include<iostream> |
接下来分析一下开头的ACM1421
首先必须承认渣渣的我这道题想了很久,也是借鉴了别人的思路。
先申明用a[n]来存储输入的数据,用dp[i][j]表示从i个数中取j对时的最小值,例如dp[3][1]表示从3个数取1对的最小疲劳度(当然三个数的时候也只能取一对)
我想从题意中应该很容易想到给数组a进行排序
1 | sort(a,a+n); |
我们先从简单的找找规律:
当只有两个数的时候,结果显然等于(a[2]-a[1])*(a[2]-a[1]),根据前面的定义我们把结果记作dp[2][1];
当有三个数,要么a[1]和a[2],要么a[2]和a[3]组合,我们取两个组合的最小值,因此:
1 | dp[3][1]=min(dp[2][1],(a[3]-a[2])*(a[3]-a[2])); |
我们接着在推一个当四个数的时候,此时我们可以选取一对或者两对,
此时选取一对可以分成两类:1
21.不包括第四个数时,dp[4][1]=dp[3][1];
2.包括第四个数时,dp[4][1]=(a[4]-a[3])*(a[4]-a[3]);
选取两对:
1 | dp[4][2]=dp[2][1]+(a[4]-a[3])*(a[4]-a[3]); |
不知道你们发现什么没(如果还没有,可以在推一两组找找规律)
1 | dp[i][j]=min(dp[i-1][j],dp[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]); |
既然找到了状态转移方程,我们就根据题意进行一些细节的处理
1 | #include<iostream> |