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 7→3→8→7→5 的路径产生了最大
输入格式
第一个行一个正整数 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
1≤r≤1000,所有输入在
[
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的小鱼之后总结出来的笔记,侵权必删