【HZNU Summer training】POJ1651-Multiplication Puzzle(区间DP)

    此题在之前的训练中一共出现了三次……然而每次都没能搞懂是怎么回事,非常丢人,这次终于知其所以然。

    题意是:给出一串数组,每次取出其中一个数(头尾除外),并把这个数和它两边的数乘积加到结果中,直到所有数被取完为止(除了头尾的数),计算能够得到的最小结果。

    这一题需要用区间DP解决。所谓区间DP,即枚举区间长度,在每次枚举的区间中计算,最后区间长度扩散到整个数组时,就可以将最优结构继承下去。

    先上代码:

#include<iostream>
#include<set>
#include<utility>
#include<map>
#include<algorithm>
#include<queue>
#include<cstring>
#include<string>
#include<vector>
#include<cstdio>
#include<cmath>
#define INF 0x3f3f3f3f
using namespace std;
int dp[105][105],a[105];
int main() {
	//freopen("in.txt","r",stdin);
	int n;
	while(cin>>n) {
		memset(dp,0,sizeof(dp));
		memset(a,0,sizeof(a));
		for(int i=1; i<=n; i++) {
			cin>>a[i];
		}
		for(int k=2; k<=n; k++){
			for(int i=1; i+k<=n; i++) {
				dp[i][i+k]=INF;
				for(int j=i+1; j<i+k; j++){
					dp[i][i+k]=min(dp[i][i+k],dp[i][j]+dp[j][i+k]+a[i]*a[j]*a[i+k]);
				}
			}
		}
			
		cout<<dp[1][n]<<endl;
	}
	return 0;
}

    在这个三层for循环中,用k表示区间的长度,i表示区间起点,那么i+k就可以表示区间终点。从2开始枚举。那么第一次枚举的区间中,就有i、i+1、i+2三个元素,此时是可以直接将它们乘起来得出结果的。

    但k=2的情况是最小的结构,过于特殊,我们从一般性考量。假设现在有一个比较长的区间了,那么我们在第三层for循环中,用一个变量j来定下一个中点,这样就可以以这个中点将区间划分为两部分。

    而由于动态规划的美丽的最优子结构性质,被划分出的这两部分区间,必定是已经经过计算得到过结果的,因为更短的区间已经被枚举过(被划分得到的区间长度必定比当前的区间要短),而这些更短的区间则在之前就已经被划分成更更短的两个区间了,而这之前的之前,从小到大迭代区间长度,已经把初始的三个元素乘积计算好,只需一步一步扩散再更新,因此相当于运用了化归思想。

    那么我们可以将被划分出的这两段区间中的元素取完,只剩下a[i]、a[j]、a[i+k]这三个元素,这时候将它们相乘,再与之前取两段区间得到的结果相加,就可以得到计算结果,再跟已经保存的dp[i][i+k]比较,更新一下最小值,然后静静等待区间扩散到整个数组(也就是k逐渐变成n),就能够得到最终的结果。

    此时可以写出状态转移方程:

dp[i][i+k]=min(dp[i][i+k],dp[i][j]+dp[j][i+k]+a[i]*a[j]*a[i+k]);

    担心保存在dp数组中的数据因为某些区间部分重合而被覆盖?不用担心,最优子结构已经帮你把当前最优的结果继承下去了,被覆盖的是不符合题意的结果,留着也百无一用。

    这一题是比较经典的一道区间DP的题,它基于分治法的思想,结果是由各个子结构合并的到,能够帮助我们更好的理解动态规划中“最优子结构”这一重要概念。说的耿直一些,就是要相信你的代码,它已经将你需要的数据计算好存储好了,完全可以大胆取用,我想要得到中点左区间dp[i][j],那么我直接写出它,也一定是可以用的现成子结构,这就是动态规划的魅力所在。

猜你喜欢

转载自blog.csdn.net/linyiduo123/article/details/81122310