【DP总结】个人的DP总结


最近,我学习了DP(动态规划),于是心血来潮,写下一片壮美的总结。


含义

首先,什么是动态规划,度娘定义:动态规划( d y n a m i c   p r o g r a m m i n g dynamic\ programming dynamic programming )是运筹学的一个分支,是求解决策过程( d e c i s i o n   p r o c e s s decision\ process decision process )最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程( m u l t i s t e p   d e c i s i o n   p r o c e s s multistep\ decision\ process multistep decision process )的优化问题时,提出了著名的最优化原理( p r i n c i p l e   o f   o p t i m a l i t y principle\ of\ optimality principle of optimality ),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

概念引入

多阶段决策过程的最优化问题。

含有递推的思想以及各种数学原理(加法原理,乘法原理等等)。
在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。当然,各个阶段决策的选取不是任意确定的,它依赖于当前面临的状态,又影响以后的发展,当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线,如图所示:在这里插入图片描述
这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题就称为多阶段决策问题。

基本思想

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。

基础

阶段:把所给求解问题的过程恰当地分成若干个相互联系的阶段,以便于求解,过程不同,阶段数就可能不同.描述阶段的变量称为阶段变量。在多数情况下,阶段变量是离散的,用k表示。此外,也有阶段变量是连续的情形。如果过程可以在任何时刻作出决策,且在任意两个不同的时刻之间允许有无穷多个决策时,阶段变量就是连续的。在前面的例子中,第一个阶段就是点A,而第二个阶段就是点A到点B,第三个阶段是点B到点C,而第四个阶段是点C到点D。
状态:状态表示每个阶段开始面临的自然状况或客观条件,它不以人们的主观意志为转移,也称为不可控因素。在上面的例子中状态就是某阶段的出发位置,它既是该阶段某路的起点,同时又是前一阶段某支路的终点。在前面的例子中,第一个阶段有一个状态即A,而第二个阶段有两个状态B1和B2,第三个阶段是三个状态C1,C2和C3,而第四个阶段又是一个状态D。
状态转移:从一个阶段的一个状态转移到下一阶段的某个状态的一种选择行为。

适用条件

1 1 1.最优化原理(最优子结构性质) 最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。

2 2 2.无后效性 将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

3 3 3.子问题的重叠性 动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。

一般解题步骤

1、判断问题是否具有最优子结构
2、把问题分为若干个子问题(分阶段)
3、建立状态转移方程(递推公式)
4、找出边界条件
5、将已知边界值带入方程
6、递推求解

分类

动态规划大致可分为以下几类:
动归入门、资源分配、区间动归、双进程类动归、背包动归、平面动归和树形动归。
那么,接下来,就让我们一个个看吧!

1、动态规划入门

【例题一】数字三角形

【题目描述】
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

         7 
    3   8 
    8   1   0 
  2   7   4   4 
  4   5   2   6   5

在上面的样例中,从7 到 3 到 8 到 7 到 5 的路径产生了最大 。
【解析】
我们用递推来计算,把这个三角形一行一行的划分成很多阶段,令 f [ i ] [ j ] f[i][j] f[i][j]为第 i i i行第 j j j列上的点到最后一行的最大和。
状态转移方程为
f [ i ] [ j ] = m a x { f [ i − 1 ] [ j ] f [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] f[i][j]=max \begin{cases} f[i-1][j]\\ f[i-1][j-1]+a[i][j] \end{cases} f[i][j]=max{ f[i1][j]f[i1][j1]+a[i][j]
【代码展示】

#include<bits/stdc++.h>
using namespace std;
int n;
int a[1111][1111];
int b[1111][1111];
int ans=0;
void init()
{
   
	cin>>n;
	for(int i=1;i<=n;i++)
	{
   
		for(int j=1;j<=i;j++)
		cin>>a[i][j];
	}
}
void work()
{
   
	for(int i=1;i<=n;i++)
	{
   
		for(int j=1;j<=i;j++)
		{
   
			b[i][j]=max(b[i-1][j],b[i-1][j-1])+a[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
   
		if(b[n][i]>ans)
		ans=b[n][i];
	}
	cout<<ans;
}
int main()
{
   
 	init();
 	work();
	return 0;
}

【总结】
由于递推的效率比记忆化搜索要高不少,所以我们一般都用递推来写动归。

【例题二】导弹拦截

题面及题解
【总结】
通过这道题我们更好地了解到了DP的思想以及它的算法,对我们的入门有很大帮助,接下来就要进一步探索它的奥妙。

2、资源分配类

这类问题就是要把一些资源分给一些公司(人),不同的分配方法会带来不同的效益,求解怎样分配最优。

【例题一】机器分配

【题目描述】
某总公司拥有高效生产设备M台,准备分给下属的 N N N个分公司。各分公司若获得这些设备,可以为总公司提供一定的盈利。问:如何分配这 M M M台设备才能使公司得到的盈利最大?求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不得超过总设备数 M M M。其中 M < = 100 , N < = 100 。 M<=100,N<=100。 M<=100N<=100
【解析】
我们试着用 f [ i ] f[i] f[i]表示 i i i个公司能够获取的最大盈利值,很明显,我们无法列出状态转移方程,因为我们不知道还有多少机器可以让我们分配,所以我们应该用一个二维数组 f [ i ] [ j ] f[i][j] f[i][j]来表示 i i i个公司总共得到了 j j j台机器所获得的最优值。
那么我们就可以很简单的列出状态转移方程
f [ i ] [ j ] = m a x { f [ i − 1 ] [ k ] + a [ i ] [ j − k ] } , 0 < = k < = j f[i][j]=max\{ f[i-1][k]+a[i][j-k] \},0<=k<=j f[i][j]=max{ f[i1][k]+a[i][jk]},0<=k<=j
【代码展示】

#include<bits/stdc++.h>
#define ud using namespace std
#define itn int
#define ll long long
ud;
int n,m,maxx=-132467;
int money[110][110],f[110][110];
void init()
{
   
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
   
		for(int j=1;j<=n;j++)
		scanf("%d",&money[i][j]);
		f[i][0]=0;
	}
}
void work()
{
   
	for(int i=1;i<=m;i++)
	{
   
		for(int j=0;j<=n;j++)
		{
   
			f[i][j]=f[i-1][j];\\初始值,表示第i个公司一台机器都不要
			for(int k=0;k<j;k++)
			f[i][j]=max(f[i][j],f[i-1][k]+money[i][j-k]);
		}
	}
}
int main()
{
   
	init();
	work();
	printf("%d\n",f[m][n]);
	return 0;
}

【例题二】复制书稿

【题目描述】
现在要把 m m m本有顺序的书分给 k k k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。
现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。
【解析】
我们可以模仿例题一,用 f [ i ] [ j ] f[i][j] f[i][j]表示 i i i个人抄写 j j j本书所用的最长时间。
于是,我们可以得到状态转移方程
f [ i ] [ j ] = m i n { m a x { f [ i − 1 ] [ k ] , w [ k + 1 ] [ j ] } } 0 < = k < = j f[i][j]=min\{max\{f[i-1][k],w[k+1][j]\}\} 0<=k<=j f[i][j]=min{ max{ f[i1][k],w[k+1][j]}}0<=k<=j
w [ k + 1 ] [ j ] w[k+1][j] w[k+1][j]表示从 k + 1 k+1 k+1本书到第 j j j本书的总页数;
枚举的 k k k表示把 k + 1 k+1 k+1 j j j本书分给第 i i i个人。
这道题要求输出方案,并且要保证前面的人少抄一点,我们用贪心思想,让后面的人尽可能的多抄些。
【代码展示】

#include<bits/stdc++.h>
#define ud using namespace std
#define itn int
#define ll long long
ud;
int m,k,a[600];
int f[600][600],sum[600]={
   };
void print(int i,int j)
{
   
	if(i==0)
	return;
	if(i==1)
	{
   
		cout<<1<<' '<<i<<endl;
		return;
	}
	else
	{
   
		int tmp=i,x=a[i];
		while(x+a[tmp-1]<=f[k][m]&&tmp>1)
		x+=a[--tmp];
		print(tmp-1,j-1);
		cout<<tmp<<' '<<i<<endl;
	}
}
int main()
{
   
	memset(f,10,sizeof(f));
	scanf("%d%d",&m,&k);
	for(int i=1;i<=m;i++)
	{
   
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
		f[
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值