动态规划

动态规划是在干什么
最重要的一句话:动态规划=局部最优(贪心)+递推(降价)+记录存储结果
做动态规划的时候脑子里要有两个集合,已经考察过的顶点集合S和没有考察过的顶点集合V。并且刚开始只考虑最后一个元素,假设其他元素都已经考虑过了(递推)
对于动态规划本来要考虑的问题,它可能是指数级别复杂度的,比如背包问题O(2^N)

但是,通过选取一种可以描述所有情况的状态,再通过状态之间的关系,列举所有可能的状态把它变为多项式级别。

动态规划的英文dynamic programming的programming是指用表格来记忆问题,其实是一种记忆化搜索。(DFS通过剪枝也不是严格的指数级别)

动态规划的通常做法:
1.刻画一个最优解的结构特征(设计状态)

2.递归地定义最优解的值

3.计算最优解(通常自底向上)

动态规划分类

动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。
线性动规:拦截导弹,合唱队形,挖地雷,建学校,剑客决斗等;
区域动规:石子合并, 加分二叉树,统计单词个数,炮兵布阵等;
树形动规:贪吃的九头龙,二分查找树,聚会的欢乐,数字三角形等;
背包问题:01背包问题,完全背包问题,分组背包问题,二维背包,装箱问题,挤牛奶(同济ACM第1132题)等;

线性动归
1.拦截导弹poj:最长递降子序列:用一个answer[i]记录以i为末尾的最长递降子序列

for(int i=1;i<Index;i++){
			for(int j=0;j<i;j++){
				if(a[j]>a[i])
					ans[i]=max2(ans[i],ans[j]+1);
			}
		}

2.IOI2000题目
poj1160 post office

求:在村庄内建邮局,要使村庄到邮局的距离和最小。

设有m个村庄,分别为 V1 V2 V3 … Vm, 要建n个邮局,分别为P1 P2 P3 … Pn。

在DP的问题中,经常有从m个物体中选n个物体的情况,本题显然也属于这种情况。一般可以这样考虑:假设已经选了1个,那么就成了在m-1个中选n-1个的问题了。

对于比较复杂的动归,取中间点是一个很正常的思考方式。其实我们经常遇到把它当成尾的动归,比如LIS就是以最后一个节点为尾部的动归。
状态转换方程:

dp( m,n ) = Min( dp( k,n-1 ) + L( k+1,m ) ), 其中:1 <= k<m

朱神评价:动归是一种记忆化dfs,所以我们必须对自己定义状态的含义非常清楚,否则无法找到递推的关系。网上很多定义方式是dp[i][j]前i个建j个邮局,然后L[i][j]表示i到j之间建立一个邮局的最短距离。这样看起来有道理,但是找递推方式的时候将会非常麻烦。首先,由于不知道每个邮局的位置,最小性变得很奇怪,L[i][j]的邮局位置是变化的,所以并不知道会不会有前面的位置离最后一个邮局位置更近。

注意:
这题虽然看起来很平淡,但是其实这个状态方程看起来后效性是很大的。因为

1.k+1到m之间的邮局建立在哪里?

2.会不会出现k+1->m之间的点到前面k个点中的最后一个邮局比我们在k+1到m中选定的邮局更近?

解答:

我们的做法:L[i][j]表示把邮局建在i,j点,负责i到j的所有村庄所耗费的距离,这样,最短的两个邮局一定是i到j中的一个(注意i,j是已经建立邮局的,我们可以通过一个判断找到其中某个位置应该由哪个邮局负责)。dp[i][j] 前i个村庄用j个邮局负责的距离,并且第j个位置是一定建立一个邮局的。(这样能保证前i个点一定是由这j个邮局负责的)

1.会不会邮局建在k+1到m之间的某个i使得情况更小?的确可能,但是这种情况在之前的前k个建立j个邮局的时候考虑过了

最后的结果应该是min{dp[k][p]+dis[k]}(dis[k]表示从k到N的村庄都由k负责)

AC代码:

#include <iostream>
#include <cmath>
using namespace std;
const int maxn=1020;
const int INF=0xffffff;
int village[maxn];
int dp[maxn][maxn];
int sum[maxn][maxn];
int dis[maxn];
int N,P;
int main(int argc, char const *argv[])
{
	cin>>N>>P;
	for(int i=1;i<=N;i++){
		cin>>village[i];
	}
	for(int i=1;i<=N;i++){
		for(int j=1;j<=N;j++){
			for(int k=i;k<=j;k++){//both i and j have post offices
				sum[i][j]+=min(abs(village[k]-village[i]),abs(village[k]-village[j]));
			}
		}
	}
	for(int i=1;i<=N;i++){
		for(int j=1;j<=i;j++){
			dp[i][1]+=abs(village[j]-village[i]);
		}
	}
	for(int i=1;i<=N;i++){
		for(int j=2;j<=P;j++){
			if(i<=j) continue;
			int MIN=INF;
			for(int k=j-1;k<i;k++){
				if(MIN>dp[k][j-1]+sum[k][i]){
					MIN=dp[k][j-1]+sum[k][i];
				}
			}
			dp[i][j]=MIN;
		}
	}
	for(int i=1;i<=N;i++){
		for(int j=i+1;j<=N;j++){
			dis[i]+=village[j]-village[i];
		}
	}
	int MIN=INF;
	for(int k=P;k<=N;k++){
		if(dp[k][P]+dis[k]<MIN)
			MIN=dp[k][P]+dis[k];
	}
	cout<<MIN<<endl;
	return 0;
}

这个代码的实现其实有很多细节要注意:
1.熟悉了怎么定义以后,sum[i][j]和dp[i][j]应该都是很好求的,但是要注意dp的初始化应该是从dp[i][1]开始的(前i点由建立在i点的邮局负责的距离,切记不可加上i以后点由建立在i的邮局负责的距离)

2.是i<=j continue,不是 i<=P continue 否则不符合定义

3.k应该是从j-1到i列举,不应该是从1列举

3.乌龟棋

想法:
用dp[i][j][k][l]表示已经使用i,j,k,l张1234得到的最大分数
dp[i][j][k][l]=min{dp[i-1][j][k][l],dp[i][j-1][k][l],dp[i][j][k-1][l],dp[i][j][k][l-1]}+a[i+j2+l3+k*4+1];
初始化条件 dp[0][0][0][0]=a[1];并且下标有包含-1的全部INF
结果 dp[a1][a2][a3][a4]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值