区间DP入门学习

区间DP,区间上做动态规划。一个大区间必然由长度不等的小区间合并而来,在合并过程中,最基本也是动态规划的必须原则:满足最优化原理和无后效性原则,所以在确定状态转移方程时,得特别留心这两点。

区间DP区别于其它线性DP题的特点是:区间DP通过枚举区间分界点实现状态转移。

词穷了,还是写题来感受吧~~,有坑自己再补上。

入门题1:凸多边形三角划分

题意:给定一个具有N(N<=50)个顶点的凸多边形,每个顶点的权值均已知。问如何把这个凸多边形划分成N-2个互不相交的三角形,使得这些三角形顶点的权的乘积之和最小。

样例:

5
121
122
123
245
231
这是一个几何模型,我们可以将其转化为实际模型,给定N个数,首尾成环,每拿走一个数的代价是该数乘以左右相邻两个的数,问拿走N-2个数后的最小代价。

思路:1)确定子问题。一个凸多边形划分为两个小的凸多边形,再进行划分......最后合并,可以确定子问题相同,都是求每个小凸多边形的三角形顶点的权的乘积。

            2)确定状态。将1-N个顶点的凸多边形划分为(A1,A2,A3..Ak) + (Ak+1,Ak+2,Ak+3......An)两部分,只要前后两部分都是最优策略,那么必然合并后的凸多边形也是最优策略,且这两部分单独求值,互不影响。所以确定状态dp[i,j]为从顶点i到顶点j的凸多边形三角划分后得到的最优值,显示区间最优状态时dp[1,n]。

            3)列出状态转移方程式。dp[i][j] = min(dp[i][k]+dp[k][j]+vis[i]*vis[k]*vis[j]).(i

其实自己对矩阵链乘那道题还是比较感兴趣滴,不过现在还是没有懂,虽然感觉都大同小异,去看看再回来写总结。

#include
#include
#include
using namespace std;
const int maxn = 210;
#define inf 0x3f3f3f3f
int dp[maxn][maxn],vis[maxn];
int main()
{
	int n,len,j,k,i;
//	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for(i = 1; i <= n; i ++)
		scanf("%d",&vis[i]);
	memset(dp,0,sizeof(dp));
	for(len = 2; len <= n-1; len++)//区间长度 
	{
		for(i = 1; i+len <= n; i ++)//枚举每次区间起点 
		{
			j = i+len;//区间终点 
			dp[i][j] = inf;//存储区间[i,j]的最优值 
			for(k= i+1; k < j; k ++)//枚举每一个分界点K 
			{
				dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]+vis[i]*vis[k]*vis[j]);
			}
		}
	}
	printf("%d\n",dp[1][n]);//显然最优值在区间[1,n] 
	return 0;
}

入门题2:矩阵链乘(MCM)

给出n个矩阵组成的序列,设计一种方法把它们依次相乘,使得总的运算量最小。假设第i个矩阵Ai=pi-1*pi。

样例

3
10 30 5 60
3
10 20 5 4

输出

4500
((A1A2)A3)
1200
(A1(A2A3))

思路:矩阵相乘满足结合律,运算量随着运算顺序改变而改变。个人理解,要使运算量最小等价于先找到最优运算顺序,平时我们都是用括号划分运算顺序,因此在此题中,我们需要找到括号划分的最优位置,即分界点K的最优位置。一条有N个矩阵的矩阵链,可以划分为[1,k]和[k+1,n]两条次矩阵链,即最大区间问题转移为求两小区间最优值合并问题,在合并过程过,要找到最优分界点,需要遍历整个矩阵链位置,假设dp[i,j]为矩阵链第i个矩阵到第j个矩阵的最优运算量,可列得状态转移方程:

    dp[i][j] = min(dp[i][k]+dp[k+1][j]+vis[i-1]*vis[k]*vis[j]) (i<=k

最后加上的是最后一次矩阵乘法的运算量,比如((A*B)*(C*D)),以B作为分界点,在合并最后一步,我们必然得加上(A*B)矩阵*(C*D)矩阵的运算量。

学习过程中的几个自己想到的思考点:

1.为什么例题1枚举矩阵区间长度从2开始,而此题从1开始?因为三角形顶点最少需要3个顶点才能进行状态转移,而此题中两个矩阵也可进行状态转移。

2.为什么例题1分界点可以重合,而此题不可以?因为划分凸多边形时,只要三角形不相交,必然存在顶点重合,此题矩阵相乘时,分界点矩阵只能位于一个矩阵链区间,否则会重复多乘,影响正确的状态转移。

3.为什么例题1分界点i

输出最优解就不用再写了,自己明白就行。

#include
#include
#include
using namespace std;
const int maxn = 210;
#define inf 0x3f3f3f3f
int dp[maxn][maxn],path[maxn][maxn],vis[maxn];
void Print_path(int i,int j)
{
	if(i == j)//k初始等于i,故递归边界是i==j 
		printf("A%d");
	else
	{
		printf("(");
		Print_path(i,path[i][j]);//输出k边界左区间矩阵链 
		Print_path(path[i][j]+1,j);//输出k边界右区间矩阵链 
		printf(")");
	}
	return;
}
int main()
{
	int n;
	int i,j,k,len,m;
//	freopen("in.txt","r",stdin);
	while(scanf("%d",&n)!=EOF)
	{
		for(i = 0; i <= n; i ++)//存储n个矩阵的行列值,矩阵Ai = P(i-1)*Pi,故需要n+1个值 
			scanf("%d",&vis[i]);
		memset(dp,0,sizeof(dp));
		memset(path,0,sizeof(path));
		for(len = 1; len < n; len ++)//枚举区间长度 
		{
			for(i = 1; i+len <= n; i ++)//取到第n个矩阵 
			{
				j = i+len;
				dp[i][j] = inf;
				for(k = i; k < j; k ++)//i<=k m)//如果分界点k能够降低运算量,则状态转移 
					{
						dp[i][j] = m;
						path[i][j] = k;//记录区间[i,j]的分界点k 
					}
				}
			}
		}
		printf("%d\n",dp[1][n]);//最优值区间 
		Print_path(1,n);
		printf("\n");
	}
	return 0;
}

你可能感兴趣的:(学习笔记)