动态规划

计算机归根结底只是会穷举,动态规划就是机智的进行穷举
先来看看生活中经常遇到的事吧——假设您是个土豪,身上带了足够的1、5、10、20、50、100元面值的钞票。现在您的目标是凑出某个金额w,需要用到尽量少的钞票。

依据生活经验,我们显然可以采取这样的策略:能用100的就尽量用100的,否则尽量用50的……依次类推。在这种策略下,666=6×100+1×50+1×10+1×5+1×1,共使用了10张钞票
贪心算法
贪心算法就是从最大的算起,把最大的用完才接着用较小面值的。
鼠目寸光
动态规划是自底向上
由循环迭代完成计算
在这里我们发现,贪心是一种只考虑眼前情况的策略。

那么,现在我们怎样才能避免鼠目寸光呢?

如果直接暴力枚举凑出w的方案,明显复杂度过高。太多种方法可以凑出w了,枚举它们的时间是不可承受的。我们现在来尝试找一下性质。

重新分析刚刚的例子。w=15时,我们如果取11,接下来就面对w=4的情况;如果取5,则接下来面对w=10的情况。我们发现这些问题都有相同的形式:“给定w,凑出w所用的最少钞票是多少张?”接下来,我们用f(n)来表示“凑出n所需的最少钞票数量”。

那么,如果我们取了11,最后的代价(用掉的钞票总数)是多少呢?
  明显cost=f(4)+1=4+1=5 ,它的意义是:利用11来凑出15,付出的代价等于f(4)加上自己这一张钞票。现在我们暂时不管f(4)怎么求出来。
  依次类推,马上可以知道:如果我们用5来凑出15,cost就是cost=f(10)+1=2+1=3 。

那么,现在w=15的时候,我们该取那种钞票呢?当然是各种方案中,cost值最低的那一个!

- 取11:cost=f(4)+1=4+1=5
  - 取5:cost=f(10)+1=2+1=3
  - 取1: cost=f(14)+1=4+1=5

显而易见,cost值最低的是取5的方案。我们通过上面三个式子,做出了正确的决策!

这给了我们一个至关重要的启示——f(n) 只与f(n-1),f(n-5),f(n-11)相关;更确切地说:
min{f(n-1),f(n-5),f(n-11)}+1

我们能这样干,取决于问题的性质:求出f(n),只需要知道几个更小的f©。我们将求解f©称作求解f(n)的“子问题”。
  这就是DP(动态规划,dynamic programming).
  将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解。
 

ADV-144. 01背包

问题描述
  给定N个物品,每个物品有⼀一个重量量W和⼀一个价值V.你有⼀一个能装M重量量的背包.问怎么装使得所装
价值最⼤大.每个物品只有⼀一个.
输⼊入格式
  输⼊入的第⼀一⾏行行包含两个整数n, m,分别表示物品的个数和背包能装重量量。
  以后N⾏行行每⾏行行两个数Wi和Vi,表示物品的重量量和价值
输出格式
  输出1⾏行行,包含⼀一个整数,表示最⼤大价值。
样例例输⼊入
3 5
2 3
3 5
4 7
样例例输出
8
数据规模和约定
  1<=N<=200,M<=5000.
思考:
dp[i][j]表示前i件物品选择部分装入2包包容量为j后,背包当前的最大价值
一共有n件物品,那么dp[n][m]就是前n件物品装入背包容量为m的背包后,背包内物品的最大值
1、当物品的体积大于背包容量时,选择不放进去dp[i][j]=dp[i-1][j]
2、当物品的体积小于或者等于时,可以选择放或者不放·,选择其最大值dp[i][j]=max(dp[i-1][j],dp[i][j-w]+v)

#include<iostream>
#include<cmath>
using namespace std;
int dp[201][5001];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int w,v;
		cin>>w>>v;
		for(int j=1;j<=m;j++){
			if(j>=w)
				dp[i][j]=max(dp[i-1][j],dp[i][j-w]+v);
			else
				dp[i][j]=dp[i-1][j];
		}
		
	}
	cout<<dp[n][m];
	return 0;
} 

ADV-148. 排队打⽔水问题(贪⼼心)

问题描述
有n个⼈人排队到r个⽔水⻰龙头去打⽔水,他们装满⽔水桶的时间t1、t2…………tn为整数且各不不相等,应如何安
排他们的打⽔水顺序才能使他们总共花费的时间最少?
输⼊入格式
第⼀一⾏行行n,r (n<=500,r<=75)
第⼆二⾏行行为n个⼈人打⽔水所⽤用的时间Ti (Ti<=100);
输出格式
最少的花费时间
样例例输⼊入
3 2
1 2 3
样例例输出
7

ADV-156. 分分钟的碎碎念动态规划

问题描述
  以前有个孩⼦子,他分分钟都在碎碎念。不不过,他的念头之间是有因果关系的。他会在本⼦子⾥里里记录
每⼀一个念头,并⽤用箭头画出这个念头的来源于之前的哪⼀一个念头。翻开这个本⼦子,你⼀一定会被互相穿
梭的箭头给搅晕,现在他希望你⽤用程序计算出这些念头中最⻓长的⼀一条因果链。
  将念头从1到n编号,念头i来源于念头from[i],保证from[i]<i,from[i]=0表示该念头没有来源念
头,只是脑袋⼀一抽,灵光⼀一现。
输⼊入格式
  第⼀一⾏行行⼀一个正整数n表示念头的数量量
  接下来n⾏行行依次给出from[1],from[2],…,from[n]
  输出格式
  共⼀一⾏行行,⼀一个正整数L表示最⻓长的念头因果链中的念头数量量
样例例输⼊入
8
0
1
0
3
2
4
2
4
样例例输出
3
样例例说明
  最⻓长的因果链有:
  1->2->5 (from[5]=2,from[2]=1,from[1]=0)
  1->2->7 (from[7]=2,from[2]=1,from[1]=0)
  3->4->6 (from[6]=4,from[4]=3,from[3]=0)
  3->4->8 (from[8]=4,from[4]=3,from[3]=0)
数据规模和约定
  1<=n<=1000

思考:
dp[i]=dp[from[i]]+1;
在这里插入图片描述

#include<iostream>
#include<vector>
using namespace std;
int main(){
	int n;
	cin>>n;
	int from[n];
	for(int i=1;i<=n;i++)
		cin>>from[i];
	int dp[n]={0};
	int maxvalue=0;
	for(int i=1;i<=n;i++)
	{
		dp[i]=dp[from[i]]+1;
		maxvalue=max(maxvalue,dp[i]);
	}
	cout<<maxvalue;
	
	return 0;
}

ADV-165. 超级玛丽(动态规划、递推)

问题描述
  ⼤大家都知道”超级玛丽”是⼀一个很善于跳跃的探险家,他的拿⼿手好戏是跳跃,但它⼀一次只能向前跳
⼀一步或两步。有⼀一次,他要经过⼀一条⻓长为n的⽺羊肠⼩小道,⼩小道中有m个陷阱,这些陷阱都位于整数位
置,分别是a1,a2,….am,陷⼊入其中则必死⽆无疑。显然,如果有两个挨着的陷阱,则玛丽是⽆无论如何也
跳过不不去的。
  现在给出⼩小道的⻓长度n,陷阱的个数及位置。求出玛丽从位置1开始,有多少种跳跃⽅方法能到达胜
利利的彼岸(到达位置n)。
输⼊入格式
  第⼀一⾏行行为两个整数n,m
  第⼆二⾏行行为m个整数,表示陷阱的位置
输出格式
  ⼀一个整数。表示玛丽跳到n的⽅方案数
样例例输⼊入
4 1
2
样例例输出
1
数据规模和约定
  40>=n>=3,m>=1
  n>m;
  陷阱不不会位于1及n上

思考:
这里跟铺地砖一样,因为她只能往前跳跃1或者2步,所以v[i]=v[i-1]+v[i-2]即可

#include<iostream>
#include<vector>
using namespace std;
int main(){
	int n,m;
	cin>>n>>m;
	vector<int>v(n+1,-1);
	for(int i=0;i<m;i++)
	{
		int temp;
		cin>>temp;
		v[temp]=0;
	}
	v[1]=1;
	if(v[2]!=0)
		v[2]=1;
	for(int i=3;i<=n;i++)
	{
		if(v[i]!=0)
			v[i]=v[i-1]+v[i-2];
	}
	cout<<v[n];
	return 0;
}

ADV-166. 聪明的美⻝⾷食家

问题描述
  如果有⼈人认为吃东⻄西只需要嘴巴,那就错了了。
  都知道⾆舌头有这么⼀一个特性,“由简⼊入奢易易,由奢如简难”(据好事者考究,此规律律也适合许多其
他情况)。具体⽽而⾔言,如果是甜⻝⾷食,当你吃的⻝⾷食物不不如前⾯面刚吃过的东⻄西甜,就很不不爽了了。
  ⼤大宝是⼀一个聪明的美⻝⾷食家,当然深谙此道。⼀一次他来到某⼩小吃⼀一条街,准备从街的⼀一头吃到另⼀一
头。为了了吃得爽,他⼤大费周章,得到了了各种⻝⾷食物的“美味度”。他拒绝不不爽的经历,不不⾛走回头路路⽽而且还
要爽歪歪(爽的次数尽量量多)。
输⼊入格式
  两⾏行行数据。
  第⼀一⾏行行Z为⼀一个整数n,表示⼩小吃街上⼩小吃的数量量
  第⼆二⾏行行为n个整数,分别表示n种⻝⾷食物的“美味度”
输出格式
  ⼀一个整数,表示吃得爽的次数
样例例输⼊入
10
3 18 7 14 10 12 23 41 16 24
样例例输出
6
数据规模和约定
  美味度为0到100的整数
  n<1000
思考:
求最⻓不降子序列⽤动态规划解决建立⼀一个与序列等长的数组b~b[i]表示当前i处能够构成的
最长不降子序列的长度~
所以说当前b[i]的值为前面所有数字比i处数字小的长度的最大值+1~
最后返回整个b数组中的最大值~~

#include<iostream>
using namespace std;
int main(){
	int n;
	cin>>n;
	int a[n];
	int dp[n]={0};
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dp[1]=1;
	int ans=1;
	for(int i=1;i<=n;i++){
		int maxvalue=0;
		for(int j=i-1;j>=1;j--){
			if(a[i]>=a[j])
				{maxvalue=dp[j];
				break;
				}
		}
		dp[i]=maxvalue+1;
		ans=max(dp[i],ans);
	}
	cout<<ans;
	return 0;
} 

ADV-205. 拿糖果(动态规划)!

问题描述
妈妈给⼩小B买了了N块糖!但是她不不允许⼩小B直接吃掉。
  假设当前有M块糖,⼩小B每次可以拿P块糖,其中P是M的⼀一个不不⼤大于根号下M的质因数。这时,妈
妈就会在⼩小B拿了了P块糖以后再从糖堆⾥里里拿⾛走P块糖。然后⼩小B就可以接着拿糖。
  现在⼩小B希望知道最多可以拿多少糖。
输⼊入格式
  ⼀一个整数N
输出格式
  最多可以拿多少糖
样例例输⼊入
15
样例例输出
6
数据规模和约定
N <= 100000
分析:动态规划问题~~⾸首先呢~创建⼀一个满⾜足不不⼤大于根号下最⼤大值MAXN的素数表,然后对素数表⾥里里
⾯面的数逐个遍历~
构建⼀一个dp[i]数组,表示当糖果数量量为i的时候所能拿的最多的糖果数量量~
对于dp[i]的值:因为⼩小B只能每次拿不不⼤大于根号下i的质因数,遍历素数表中满⾜足条件的素数
(prime[j] <= sqrt(i) && i % prime[j] == 0),更更新dp[i]的值为(dp[i-2prime[j]] + prime[j])的最
⼤大值~
即:dp[i] = max(dp[i], dp[i-2
prime[j]] + prime[j]);

#include<iostream>
#include<cmath>
using namespace std;
int prime[5000];
int dp[100005];
int book[10005];
int cnt=0;
void create(){
	for(int i=2;i<sqrt(10005);i++){
		if(book[i]==0){
			prime[cnt++]=i;
			for(int j=i*i;j<=sqrt(10005);j=j+i)
				book[j]=1;
		}
	}
}
int main(){
	create();
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=0;j<cnt;j++){
			if(prime[j]>sqrt(i))
				break;
			if(i%prime[j]==0)
				dp[i]=max(dp[i],dp[i-2*prime[j]]+prime[j]);
		}
	}
	cout<<dp[n];
	return 0;
}

ADV-202. 最⻓长公共⼦子序列列(动态规划)

问题描述
  给定两个字符串串,寻找这两个字串串之间的最⻓长公共⼦子序列列。
输⼊入格式
  输⼊入两⾏行行,分别包含⼀一个字符串串,仅含有⼩小写字⺟母。
输出格式
  最⻓长公共⼦子序列列的⻓长度。
样例例输⼊入
abcdgh
aedfhb
样例例输出
3
样例例说明
  最⻓长公共⼦子序列列为a,d,h。
数据规模和约定
  字串串⻓长度1~1000、

思考:
在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<cstring>
using namespace std;
int main(){
	string s1,s2;
	cin>>s1>>s2;
	int len1=s1.length();
	int len2=s2.length();
	int dp[len1+1][len2+1];
	for(int i=0;i<=len1;i++){
		for(int j=0;j<=len2;j++){
			if(i==0||j==0)
				dp[i][j]=0;
			else if(s1[i]==s2[j])
				dp[i][j]=dp[i-1][j-1]+1;
			else
				dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		}
	} 
	cout<<dp[len1][len2];
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值