真正的动态规划入门,从暴力递归再到记忆化搜索再到动态规划

Dp入门DFS->记忆化搜索->Dp

/*一个楼梯共有 n级台阶,每次可以走一级或者两级,问从第 0级台阶走到第 n级台阶一共有多少种方案。
输入格式
共一行,包含一个整数 n
输出格式
共一行,包含一个整数,表示方案数。
数据范围
1≤n≤15*/

https://www.acwing.com/problem/content/823/

DFS暴力递归搜索:O(O^O)

#include<iostream>
using namespace std;
#define ll long long
ll n;//表示有n节台阶
ll dfs(ll x){
    if(x==0)return 0;
    if(x==1)return 1;
    if(x==2)return 2;
    else{
        return dfs(x-1)+dfs(x-2);
    }
}
void main(){
    cin>>n;
    cout<<dfs(n);
}
究其原因:重复化计算例如下图–>左节点三与右节点三的数值重复了,重复计数了;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7OkAjRHE-1681824193688)(C:/Users/xue/AppData/Roaming/Typora/typora-user-images/image-20230416215658418.png)]

记忆化搜索

改进思路:空间<—>时间互换;利用一个记忆数组(mem)来存储已经获取过的每一个节点
#include<iostream>
using namespace std;
#define ll long long
#define N 10010/*---增加的东西---定义数组长度-*/
ll n;
ll mem[N] = {};/*---增加的东西---记忆数组-*/
ll dfs(ll x) {
    if(x==0)return 0;
    if (mem[x])return mem[x];/*---增加的东西---如果mem存在,则直接返回mem记录的值,不用再计算以下的值-*/
    /*如上图的3与3重复了,就等于三已经计数过了,直接返回mem【3】的记忆数组即可*/
    ll sum = 0;/*---增加的东西---temp计数值-*/
    if (x == 1)sum = 1;
    if (x == 2)sum = 2;
    else {
        sum = dfs(x - 1) + dfs(x - 2);
    }
    mem[x] = sum;/*---增加的东西---记录当前节点x以下的节点值-*/
    return sum;
}
void main() {
    cin >> n;
    cout << dfs(n);
}

DP动态规划(将栈的空间节省掉)

"记忆化搜索"为递归操作,“栈”的空间也十分珍贵所以可以选择变成递推的方式来运行

#include<iostream>
using namespace std;
#define ll long long
#define N 10010/*---增加的东西---定义数组长度-*/
ll n;
ll mem[N] = {};/*---增加的东西---记忆数组-*/
ll dfs(ll x) {
    if (x == 0)return 0;
    if (mem[x])return mem[x];/*---增加的东西---如果mem存在,则直接返回mem记录的值,不用再计算以下的值-*/
    /*如上图的3与3重复了,就等于三已经计数过了,直接返回mem【3】的记忆数组即可*/
    ll sum = 0;/*---增加的东西---temp计数值-*/
    if (x == 1)sum = 1;
    if (x == 2)sum = 2;
    else {
        sum = dfs(x - 1) + dfs(x - 2);
    }
    mem[x] = sum;/*---增加的东西---记录当前节点x以下的节点值-*/
    return sum;
}
void main() {//保留上述dfs函数的原因是主要部分都是相同的,只不过换成递推的了
    cin >> n;
    //设计边界条件
    if (n == 0)cout << 0 << endl;
    else if (n == 1)cout << 1 << endl;
    else if (n == 2)cout << 2 << endl;
    else {//进入DP
        mem[1] = 1; mem[2] = 2;//设置初始值
        for (int i = 3; i <= n; i++) {
            //因为上述函数中的dfs到最后也是变为mem[1]=1;mem[2]=2;的场景,那我就直接用最底下的元素来开始推上去
            mem[i] = mem[i - 1] + mem[i - 2];
        }
        //直接检查最后一个到达的数量就可以获取全部的数量;
        cout << mem[n] << endl;
    }
}

DP动态规划(将数组的空间节省掉)

如果我们仅仅需要所有的线路总和,那么每一个节点数的线路数就用不上了可以用一个temp临时数直接划掉
主要代码
for (int i = 3; i <= n; i++) {
    newf = temp1 + temp2;//获取可进位
	temp1 = temp2;//进位
	temp2 = nfw;//进位
}
整体代码
#include<iostream>
using namespace std;
#define ll long long
int main()
{
	ll nfw;
	ll n;
	ll temp1, temp2;
	cin >> n;
	temp1 = 1;
	temp2 = 2;
	if (n == 0)cout << 0 << endl;
	else if (n == 1)cout << 1 << endl;
	else if (n == 2)cout << 2 << endl;
	else {
		for (int i = 3; i <= n; i++) {
			nfw = temp1 + temp2;
			temp1 = temp2;
			temp2 = nfw;
		}
		cout << nfw << endl;
	}
	return 0;

}

DP最优子问题

dfs的参数参数应该尽可能的少,不应把没有影响到边界的参数放进来

暴力dfs

#include<iostream>
usign namesapce std;
int n;
int arr[1000]={};
int dfs(int x)
{
    if(x>n)return 0;
    else return max(dfs(x+1),dfs(x+2)+arr[x]);
}
int main()
{
    
    return 0
}

记忆化搜索(加入mem[1000])

#include<iostream>
usign namesapce std;
int n;
int arr[1000]={};
int mem[1000]={};
int dfs(int x)
{
    int temp;
    if(mem[x])return mem[x];//新的,因为mem已经计算过了,肯定是一样的
    if(x>n)temp=0;
    else temp= max(dfs(x+1),dfs(x+2)+arr[x]);
    mem[x]=temp;
    return temp;
}
int main()
{
    int n=0;
    cin>>n;
    while(n--){
        cin>>n;
        for(int i=0;i<n;i++){
            cin>>arr[i];
        }
        memset(mem,0,sizeof mem);//初始化记忆数组
        int temp2=dfs(1);
        cout<<temp2<<endl;
    }
    return 0
}

DP动态规划优化

从n到1往回推
#include<iostream>
#include<math.h>
using namespace std;
#define ll long long
int main() {
	ll n;
	ll arr[1000] = {};
	cin >> n;
	ll mem[1000] = {};
	ll temp;
	while (n--)
	{
		cin >> temp;
		ll temp1 = 0, temp2 = 0, temp3 = 0;
		for (ll i = 0; i < temp; i++) {
			cin >> arr[i];
		}
		for (int j = 0; j <temp; j++) {
			temp1 = max(temp2, temp3 + arr[j]);
			temp3 = temp2;
			temp2 = temp1;
		}
		cout << temp1;
	}
	return 0;
}

经典三角形问题

[USACO1.5][IOI1994]数字三角形 Number Triangles

题目描述

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

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

在上面的样例中,从 7 → 3 → 8 → 7 → 5 7 \to 3 \to 8 \to 7 \to 5 73875 的路径产生了最大

输入格式

第一个行一个正整数 r r r ,表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

输出格式

单独的一行,包含那个可能得到的最大的和。

样例 #1

样例输入 #1

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

样例输出 #1

30

提示

【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ r ≤ 1000 1\le r \le 1000 1r1000,所有输入在 [ 0 , 100 ] [0,100] [0,100] 范围内。

题目翻译来自NOCOW。

USACO Training Section 1.5

IOI1994 Day1T1

[P1216 USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

记忆化搜索

#include<iostream>
#include<math.h>
using namespace std;
#define ll long long
ll mem[1010][1010] = {};
ll arr[1010][1010] = {};
ll n;
ll dfs(ll x1, ll x2)
{
	if (mem[x1][x2])return mem[x1][x2];
	ll temp = 0;
	if (x1 >n  || x2 >x1)temp=0;
	else {//因为他没有选和不选的区别,所以+arr[][]在max()的后面
		temp = max(dfs(x1 + 1, x2), dfs(x1 + 1, x2 + 1)) + arr[x1][x2];
	}
	mem[x1][x2] = temp;
	return temp;
}
int main()
{
	cin >> n;
	for (ll i = 1; i <= n; i++) {
		for (ll j = 1; j <= i; j++) {
			cin >> arr[i][j];
		}
	}
	ll ret = dfs(1, 1);
	cout << ret<< endl;
	return 0;
}

动态规划

#include<iostream>
#include<math.h>
using namespace std;
#define ll long long
ll mem[1010][1010] = {};
ll arr[1010][1010] = {};
ll n;
int main()
{
	cin >> n;
	for (ll i = 1; i <= n; i++) {
		for (ll j = 1; j <= i; j++) {
			cin >> arr[i][j];
		}
	}
	for (ll i = n; i >= 1; i--) {
		for (ll j = 1; j <= n; j ++)
			mem[i][j] = max(mem[i + 1][j], mem[i + 1][j + 1]) + arr[i][j];
	}
	cout << mem[1][1] << endl;
	return 0;
}

经典01背包

有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。

第i件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N行,每行两个整数 vi,wi,用空格隔开,分别表示第 i件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000

0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

先是暴力的dfs

写的时候注意哪一个是价值,哪一个是重量
#include<iostream>
#include<math.h>
using namespace std;
int n;
int v[1010] = {}, w[1010] = {}, mem[1010][1010] = {};//有几个边界就写几维的mem数组//这个代码块没用到
// 暴力dfs
int dfs(int x, int V){//写出影响边界的条件,现阶段有v--重量 w---价值爽边界
	if (x >= n)return  0;
	else if (v[x] > V) return  dfs(x + 1, V);//如果没有容量了就跳过,不能直接不写,错误点之一
	else {
		return  max(dfs(x + 1, V), dfs(x + 1, V - v[x]) + w[x]);
	}
}

int main()
{
	int V;
	cin >> n;
	cin >> V;
	for (int i = 0; i < n; i++)
	{
		cin >> v[i];
		cin >>w[i];
	}
	cout << dfs(0,V) << endl;
	return 0;
}

记忆化搜索优化

#include<iostream>
#include<math.h>
using namespace std;
int n;
int v[1010] = {}, w[1010] = {}, mem[1010][1010] = {};/*---增加的东西---记忆数组一维重量二维价值-*/

//CODEING后
int dfs(int x, int V)
{
	if (mem[x][V])return mem[x][V];/*---增加的东西---记忆数组存在则立即返回-*/
	int temp = 0;
	if (x >= n)temp = 0;//边界条件是大于等于
	else if (v[x] > V) temp = dfs(x+1,V);//如果没有容量了就跳过,不能直接不写,错误点之一
	else {
		temp = max(dfs(x + 1, V), dfs(x + 1, V - v[x]) + w[x] );
	}
	mem[x][V] = temp;
	return temp;
}
int main()
{
	int V;
	cin >> n;
	cin >> V;
	for (int i = 0; i < n; i++)
	{
		cin >> v[i];
		cin >>w[i];
	}
	cout << dfs(0,V) << endl;
	return 0;
}

DP动态规划(逆序推回来)

#include<iostream>
#include<math.h>
using namespace std;
int n;
int v[1010] = {}, w[1010] = {}, mem[1010][1010] = {};
int main()
{
	int V;
	cin >> n;
	cin >> V;
	for (int i = 1; i <=n; i++)
	{
		cin >> v[i];
		cin >>w[i];
	}
	for (int i = n; i >= 1; i--) {//表示每个物品
		for (int j = 0; j <=V; j++) {//表示此时的背包容量
			if (j < v[i]) {//表示如果背包容量少于当前物品容量
				mem[i][j] = mem[i + 1][j];//就不拿当前的,跳过当前的物品
			}
			else if(j>=v[j]) {//表示如果背包容量大于或者等于当前物品
				mem[i][j] = max(mem[i+1][j],mem[i+1][j-v[i]]+w[i]);//主要是抄上面的递归公式,此时有两个子问题,拿与不拿
				//不拿就如同上面那一个,拿就减去背包容量,加上物品价值
			}
		}
	}
	cout << mem[1][V] << endl;//表示
	return 0;
}

DP动态规划(递推从头开始推过去)//这个跟直觉差不多

#include<iostream>
#include<math.h>
using namespace std;
int n;
int v[1010] = {}, w[1010] = {};
int mem[1010][1010] = {};//mem数组的定义是:从第x个物品开始(X~n),总体积<=j的最大价值
//正序枚举从(1~x)个物品,总体积<=j的最大价值
int main()
{
	int V;
	cin >> n;
	cin >> V;
	for (int i = 1; i <=n; i++){
		cin >> v[i];
		cin >>w[i];
	}
	for (int i = 1; i <= n; i--) {/*---改变的东西--从头开始推过去--*/
		for (int j = 0; j <=V; j++) {
			if (j < v[i]) {//背包不够,只能被迫选择不选他
				mem[i][j] = mem[i - 1][j];/*---改变的东西--获取之前的信息--*/
			}
			else if(j>=v[j]) {
				mem[i][j] = max(mem[i-1][j],mem[i-1][j-v[i]]+w[i]);/*---改变的东西--子问题在之前进行判断--*/
			}
		}
	}
	cout << mem[n][V] << endl;//表示到第n个物品的时候有V个背包容量时候的可获得最大价值
	return 0;
}

DP动态规划(优化空间)

#include<iostream>
#include<math.h>
using namespace std;
int n;

int main()
{
	int V;
	cin >> n;
	cin >> V;
	for (int i = 1; i <=n; i++){
		cin >> v;
		cin >>w;
	}
	for (int i = 1; i <= n; i--) {/*---改变的东西--从头开始推过去--*/
		for (int j = 0; j <=V; j++) {
			if (j < v[i]) {//背包不够,只能被迫选择不选他
				mem[i][j] = mem[i - 1][j];/*---改变的东西--获取之前的信息--*/
			}
			else if(j>=v[j]) {
				mem[i][j] = max(mem[i-1][j],mem[i-1][j-v[i]]+w[i]);/*---改变的东西--子问题在之前进行判断--*/
			}
		}
	}
	cout << mem[n][V] << endl;//表示到第n个物品的时候有V个背包容量时候的可获得最大价值
	return 0;
}

总结

//求 最优子问题 dfs(x)=max(dfs(x+1),dfs(x+2));//意思是在两条路里面找一条优的路,但是不选
//求 子问题的和 dfs(x)=dfs(x+1)+dfs(x+2);//把所有路的节点都加起来
//求 最优子问题的选与不选 dfs(x)=max(dfs(x+1),dfs(x+2)+arr[x]);//01背包问题,选与不选
//求 最优子问题的都要选 dfs(x)=max(dfs(x+1),dfs(x+2))+arr[x];//数字金字塔问题,都要选,看哪一个更合适

版权声明:

我是看了up:一只会code的小鱼之后总结出来的笔记,侵权必删
mem[i][j] = mem[i - 1][j];/—改变的东西–获取之前的信息–/
}
else if(j>=v[j]) {
mem[i][j] = max(mem[i-1][j],mem[i-1][j-v[i]]+w[i]);/—改变的东西–子问题在之前进行判断–/
}
}
}
cout << mem[n][V] << endl;//表示到第n个物品的时候有V个背包容量时候的可获得最大价值
return 0;
}




# 总结

```c++
//求 最优子问题 dfs(x)=max(dfs(x+1),dfs(x+2));//意思是在两条路里面找一条优的路,但是不选
//求 子问题的和 dfs(x)=dfs(x+1)+dfs(x+2);//把所有路的节点都加起来
//求 最优子问题的选与不选 dfs(x)=max(dfs(x+1),dfs(x+2)+arr[x]);//01背包问题,选与不选
//求 最优子问题的都要选 dfs(x)=max(dfs(x+1),dfs(x+2))+arr[x];//数字金字塔问题,都要选,看哪一个更合适

版权声明:

我是看了up:一只会code的小鱼之后总结出来的笔记,侵权必删

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值