对7.15-7.18DP的一些个人见解与简单例题(背包,线性,区间)

前言:

最近已经好几天没更博客了,希望刘老师不要揍我(如果一定要揍的话请让岳老师揍应该下手会轻点)

最近这几天学的是DP(简直吓人woc)

相信有一些小伙伴会和我一样一脸懵逼

所以我决定,以一个菜逼的视角来总结一下DP(dalao喷轻点)

正文:

DP的概念:

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划(以上摘自度娘)

DP的分类:

大致分4种:

1.线性DP,如导弹拦截,合唱队形

2.区间DP,如石子合并,炮兵布阵

3.树形DP,如数字三角形

4.背包DP,如采药,逃亡的准备

本文主要整理的是1,2,4三种

DP的一些术语:

由于太多就不一一阐述了,如果看我的整理时一脸懵逼请戳这里

                                                                                        分  鸽  线

DP的引入:

题目描述:有一个10级台阶,每次可以跳1级或2级,一开始处于0级,问有几种跳法?

很多人一开始看到就会觉得:

这明明是一道递推/递归嘛,垃圾博主一点技术含量都没有

请神犇们装一下弱,假装什么都不知道

一开始思考这个问题,最简单的算法

其实就是暴力枚举嘛

暴力:

我们可以尝试利用排列组合的思想来暴力枚举,用一个多层嵌套循环遍历出所有的结果,每当情况符合便计数器++

代码实现:由于没有太多思维难度鱼柿不在详细阐述

但是这种算法时间复杂度是指数级别的,我们可以想一想更高效的解法

因为一次只能跳1级或者2级我们可以想到,10级的走法就为8级的走法+9级的走法

就这样往上推,我们可以设f(x)为x级的走法

可得公式:

f(x)=f(x-1)+f(x-2) (x>=3)

f(1)=1

f(2)=2

然后我就要开始乱BB了

由美丽的度娘可知,动态规划中有几个重要的概念:【最优策略】,【状态】,【状态转移方程】

该题没有什么策略,但由于每次方案是最多的,所以只要每次这样DP就是最优的

而f(x)是一个状态,是一个客观的事实

f(x)=f(x-1)+f(x-2) (x>=3)就是传说中的状态转移方程

这题其实还是偏递推的,只是给您们一个了解的东东,还没有策略(有也很少)

代码实现:自己套公式(公式都给您了您还要怎么样)

                                                                                  分  鸽  线

线性DP:

例题一:导弹拦截

该道题是一道写到烂的经典DP

我们可以用一个数组dp[i]来表示拦截到第i个导弹时,拦截了几个导弹

二重循环,如果第j个比第i个要低的话,就说明前面一定没拦到

因为最优策略,所以我们可以枚举之前的dp数组,来找一个最大的,将j作为后续

因此可列出转移方程:dp[i]=max(dp[1—j])+1;

代码实现:

#include<bits/stdc++.h>
using namespace std;
int n,a[300],b[300],c[300],sum,xts=1;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		c[i]=1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<i;j++)
		if(a[j]>=a[i]) c[i]=max(c[i],c[j]+1);
		sum=max(c[i],sum);
	}
	b[1]=a[1];
	for(int i=2;i<=n;i++)
	{
		int x=1000000,y=0;
		for(int j=1;j<=xts;j++)
		if(a[i]<=b[j]&&b[j]<x) x=b[j],y=j;
		if(y!=0) b[y]=a[i];
		else b[++xts]=a[i];
	} 
	cout<<sum<<endl<<xts;
	return 0;
}

套用一句刘老师的话:“本题第二问涉及贪心,在这里不在阐述”

例题二:合唱队形

就是求最长不上升子序列,思路同上,只要改一下代码细节即可,但这题问法有变化,注意细节就好

代码实现:

#include<bits/stdc++.h>
using namespace std;
int a[101],dps[101],dpj[101];
int n,sum;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		dps[i]=1;
		dpj[i]=1;
	}
	for(int i=1;i<=n;i++)
    for(int j=1;j<i;j++)
    {
    	if(a[j]<a[i])
    	   dps[i]=max(dps[i],dps[j]+1);
    }
    for(int i=n;i>=1;i--)
    {
    	for(int j=n;j>i;j--)
    	{
    		if(a[j]<a[i])
    		    dpj[i]=max(dpj[i],dpj[j]+1); 
    	}
    }
    for(int i=1;i<=n;i++)
    {
    	if(dps[i]+dpj[i]-1>sum) sum=dps[i]+dpj[i]-1;
    }
    cout<<n-sum<<endl;
	return 0;
} 

线性DP就到这辣

                                                                                      分  鸽  线

背包DP:

背包DP,简单来说就是在一定的情况与条件下,取物品,使他们的价值最大

背包九讲传送门

我认为这里已经写得不能再详细了,所以背包不做太多总结

但有必要说一下01背包,因为所有背包问题几乎都是它的变种

01背包:

刚拿到这个问题的时候,我第一想到的是贪心,但贪心很明显存在反例,此处不再列举

鱼柿设一个二维状态f[i][v]表示在前i件物品放入一个容量为j的背包时最大价值

可以列出转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-当前物品重]+当前物品价值}

代码实现:

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int dp[110][1100],mo[110],r[110],m,t;
	cin>>t>>m;
	for(int i=1;i<=m;i++) cin>>r[i]>>mo[i];
	for(int i=1;i<=m;i++)
	for(int j=1;j<=t;j++)
	{
		dp[i][j]=dp[i-1][j];
		if(j-r[i]>=0)
		dp[i][j]=max(dp[i-1][j],dp[i-1][j-r[i]]+mo[i]);
	}
	cout<<dp[m][t]<<endl;
	return 0;
}

                                                                                        分  鸽  线

区间DP:

区间DP,顾名思义,就是在一段区间内进行的DP

区间DP一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问

题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值

一般用来求该区间某某最小值或最大值

区间DP非常让人想掀桌子望而生畏

而且像我这种菜鸡

写出来的时间复杂度平均都是:O(n²)或O(n³)

所以就放例题咯

例题一:石子合并

刚看到这道题

我尝试使用贪心

但是没过样例(因为贪心找的是局部最优解不一定是全局最优解)

所以只能DP咯

区间DP有个万能定义

dp[i][j]是指i—j的最优值

就这样套好了

我们可以先预处理前缀和

枚举区间的左右端点

然后利用分治思想拆拆拆!!!

然后枚举它们之间的所有子区间就可以

具体方程见代码

代码实现:

#include<bits/stdc++.h>
using namespace std;
#define INF 999999999;
int n,a[301],dp[301][301];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		a[i]=a[i]+a[i-1];
	}
	for(int i=2;i<=n;i++)
	  for(int l=1;l<=n-i+1;l++)
	  {
	  	int r=i+l-1;
	  	dp[l][r]=INF;
	  	for(int k=l;k<r;k++)
	  	{
	  		dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+a[r]-a[l-1]);
	  	}
	  }
	cout<<dp[1][n]<<endl;
	return 0;
} 

                                                                                        分  鸽  线

后话:

其实我觉得这篇破博客写的不太好

没有讲的很清楚

而且风格也不好看

但是我在写的时候有觉得掌握的更好

所以还是要多写写博客嘛~

个人认为

DP只要思考思考思考就可以了

一道题不会很正常

毕竟刘老师总结了10年了还没总结好

接下来的路还很长嘛

希望这篇blog

能对您有一点点

哪怕就那么一点点

帮助

谢谢您的捧场

欢迎补充

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值