dp动态规划(希望是)

走楼梯(记忆化搜索版)

//暴力dfs+记录答案
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int memo[N];

int dfs(int n)
{
	if(memo[n])return memo[n];
	int sum=0;
	if(n==1)sum = 1;
	else if(n==2)sum= 2;
	else sum=dfs(n-1)+dfs(n-2);
	memo[n]=sum;
	return sum;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n;
	cin>>n;
	cout<<dfs(n)<<endl;
	return 0;
}

递归的过程:“归”是产生问题答案的过程。“递”是分解子问题的过程。
--->如果只保留“归”的过程(已知最小子问题的解,推出最大问题的解)--->递推。

要想实现记忆化搜索的话,那么dfs的参数就 需要 尽可能地少。
不应该把 没有影响到边界的参数 放进来。

想要剪枝,就要尽可能把能剪枝的参数写上来。

走楼梯(递推版)

//递推的公式=dfs向下递归的公式。
//递推数组的初始值=dfs的边界。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int memo[N];
int f[N];
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int n;
	cin>>n;
	f[1]=1;
	f[2]=2;
	for(int i=3;i<=n;i++)
	{
		f[i]=f[i-1]+f[i-2];
	}
	cout<<f[n]<<endl;
	return 0;
}

大盗阿福

点击查看题目
题目描述】
阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。

这条街上一共有 N 家店铺,每家店中都有一些现金。阿福事先调查得知,
只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,
然后警察就会蜂拥而至。

作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。
他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?

【输入】
输入的第一行是一个整数T(T≤50) ,
表示一共有T组数据。

接下来的每组数据,第一行是一个整数N(1≤N≤100000),
表示一共有N家店铺。第二行是N个被空格分开的正整数,
表示每一家店铺中的现金数量。
每家店铺中的现金数量均不超过1000。

【输出】
对于每组数据,输出一行。该行包含一个整数,
表示阿福在不惊动警察的情况下可以得到的现金数量。

【输入样例】
2
3
1 8 2
4
10 7 6 14
【输出样例】
8
24
【提示】
对于第一组样例,阿福选择第2家店铺行窃,
获得的现金数量为8。

对于第二组样例,阿福选择第1和4家店铺行窃,
获得的现金数量为10+14=24。

超时代码(暴力):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,t;
int memo[N];
vector<int>a(N);
int dfs(int x)//当前正在考虑那家店。
{
	if(x>n)return 0;
	else return max(dfs(x+1),dfs(x+2)+a[x]);
	//一家店考虑完了要去下一家店。
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		int res=dfs(1);
		cout<<res<<endl;
	}
	return 0;
}

记忆化数组代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,t;
int memo[N];
vector<int>a(N);
int dfs(int x)//当前正在考虑那家店。
{
	if(memo[x]) return memo[x];
	int sum=0;
	if(x>n) sum=0;
	else sum=max(dfs(x+1),dfs(x+2)+a[x]);
	//一家店考虑完了要去下一家店。
	memo[x]=sum;
	return sum;
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		memset(memo,0,sizeof(memo));
                //每次都要把memo[]记忆数组的值清空。避免影响下一次记忆。
		int res=dfs(1);
		cout<<res<<endl;
	}
	return 0;
}

递推:

//meme[i]存的是:从第i个店铺开始(i~n)能获取到的最大价值。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,t;
int memo[N];
vector<int>a(N);

signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		memset(memo,0,sizeof(memo));
		for(int i=n;i>=1;i--)
		{
			memo[i]=max(memo[i+1],memo[i+2]+a[i]);
			//还是递归的公式。
			//要倒序推回去才能计算出来。
		}
		/*
		  for(int i=1;i<=n;i++)
		  {
		  memo[i]=max(memo[i-1],memo[i-2]+a[i]);
		  }
		  cout<<memo[n];
		//但是上述代码当i=1时,i-2会越界。可以将各个[]同时加2.
		改:
                  下标偏移加二
		  for(int i=1;i<=n;i++)
		  {
		  memo[i+2]=max(memo[i+1],memo[i]+a[i]);
		  }
		  cout<<memo[n+2];
		*/
		cout<<memo[1]<<endl;
	}
	return 0;
}

进行空间优化:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,t;
vector<int>a(N);

signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		int temp=0,temp1=0,temp2=0;
		for(int i=n;i>=1;i--)
		{
			temp=max(temp1,temp2+a[i]);
			temp2=temp1;
			temp1=temp;
		}
		cout<<temp<<endl;
	}
	return 0;
}

dfs-->记忆化搜索-->倒序递推-->正序递推-->空间优化。


求最优子问题:dfs(x)=max(dfs(x+1),dfs(x+2));
求最优子问题的和:dfs(x)=dfs(x+1)+dfs(x+2);


数字三角形

https://www.luogu.com.cn/problem/P1216

分析题干,发现从上面往下一步步走很麻烦,直接搜索肯定超时。所以,逆向求解。

看样例分析:

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

若从倒数第二排的‘2’开始走,只有2个选择,往左下方和右下方。

往左下方是‘4’,得到的最终值为6,往右下方是‘5’,得到的最终值是7.这时当然选择右下方。

我们就将‘2’改写成2+5=7。 再次考虑倒数第二排的7,

同理,应选择左下,得到最终值是12。还是将‘7’改写成5+7=12。

依次类推则倒数第二排变为:

7,12,10,10
原数字三角形变为:

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

这时再考虑第三行第一个。有两种选择:左下和右下。

假设走左下方,由于这时左下的值已经是从左下开始走到底的最优值,我们不需要在选择下一步怎么走,直接加上左下的值即可。

同理,走右下时,直接加上右下的值即可。因为此时右下的值已经是从右下走到底的最优值,不需要选择了。

再比较走两条路的值,右边的值更大,选择右边的值。则第三行的第一个值更新为8+12=20。

以此类推,得到下面的数字三角形:

        7 
      3   8 
    20  13  10 
  7   12  10  10 
4   5    2    6   5

同理,更新第二排,有:

           7 
        23   21 
     20   13    10 
   7   12   10   10  
 4    5    2    6    5

最后一个了,有:

           30 
        23   21 
     20   13   10 
   7   12    10    10 
4    5    2     6      5 

起点的值保存了从起点到终点的最优值,也就是答案。详见代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int n;
	cin>>n;
	vector<vector<int>>a(n+1,vector<int>(n+1));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=n-1;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		{
			int max1=max(a[i+1][j],a[i+1][j+1]);
			a[i][j]+=max1;
		}
	}
	cout<<a[1][1]<<endl;
	return 0;
} 

超时代码:(暴力dp)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n;
int a[1005][1005];
int dfs(int x1,int y1)
{
	if(x1>n||y1>n)
	{
		return 0;
	}else
		return max(dfs(x1+1,y1),dfs(x1+1,y1+1))+a[x1][y1];
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	int res=dfs(1,1);
	cout<<res<<endl;
	return 0;
}

记忆化搜索代码(存在一个样例超时):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n;
int a[1005][1005];
int mem[N][N];
int dfs(int x1,int y1)
{
	int sum=0;
	if(mem[x1][y1])return mem[x1][y1];
	if(x1>n||y1>n)
	{
		sum=0;
	}else
		sum=max(dfs(x1+1,y1),dfs(x1+1,y1+1))+a[x1][y1];
	
	mem[x1][y1]=sum;
	return sum;
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	int res=dfs(1,1);
	cout<<res<<endl;
	return 0;
}

递推代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n;
int a[1005][1005];

int mem[N][N];
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		{
			mem[i][j]=max(mem[i+1][j],mem[i+1][j+1])+a[i][j];
		}
	}
	cout<<mem[1][1]<<endl;
	return 0;
}

正序递推:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005;
int n;
int g[N][N];
int mem[N][N];
int f[N][N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>g[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			f[i][j]=max(f[i-1][j],f[i-1][j-1])+g[i][j];
		}
	}
	int res=0;
	for(int i=1;i<=n;i++)
	{
		res=max(f[n][i],res);
	}
	cout<<res<<endl;
	return 0;
}

空间优化:(1维到2维)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n;
int a[1005][1005];
int mem[N];
signed main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		{
			mem[j]=max(mem[j],mem[j+1])+a[i][j];
		}
	}
	cout<<mem[1]<<endl;
	return 0;
}

————————————————————————————

01背包问题

每个物品都有选和不选两个选择
超时代码1:

点击查看代码
//因为sumw对限制边界条件没起作用所以在之后的正确代码中不用它
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n,m;
int v[N],w[N];
int dfs(int x,int vv,int sumw)//当前物品编号,当前背包的剩余容量,当前的总价值
{
	int res;
	if(x>n)
	{
		res=sumw;
	}else if(vv<v[x])
	{
		res=dfs(x+1,vv,sumw);
	}
	else if(vv>=v[x])
	{
		res=max(dfs(x+1,vv,sumw),dfs(x+1,vv-v[x],sumw+w[x]));//具有选或不选两个选择。
	}
	return res;
}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	int res=dfs(1,m,0);
	cout<<res<<endl;
	return 0;
}

超时代码2:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n,m;
int v[N],w[N];
int dfs(int x,int vv)
{
	if(x>n)
	{
		return 0;
	}else if(vv<v[x])
	{
		return dfs(x+1,vv);
	}
	else if(vv>=v[x])
	{
		return max(dfs(x+1,vv),dfs(x+1,vv-v[x])+w[x]);
	}
}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	int res=dfs(1,m);
	cout<<res<<endl;
	return 0;
}

记忆化搜索

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n,m;
int v[N],w[N];
int mem[N][N];
int dfs(int x,int vv)
{
	if(mem[x][vv])
		return mem[x][vv];
	int sum=0;
	if(x>n)
	{
		sum=0;
	}else if(vv<v[x])
	{
		sum=dfs(x+1,vv);
	}
	else if(vv>=v[x])
	{
		sum=max(dfs(x+1,vv),dfs(x+1,vv-v[x])+w[x]);
	}
	mem[x][vv]=sum;
	return sum;
}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	int res=dfs(1,m);
	cout<<res<<endl;
	return 0;
}

最暴力是O(2^n),记忆化搜素维O(v*n);

递推

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];

signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<=m;j++)
		{
			if(j<v[i])//如果背包不够装
			{
				f[i][j]=f[i+1][j];
			}else if(j>=v[i])
			{
				f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i]);
			}
		}
	}
	cout<<f[1][m]<<endl;
	return 0;
}

正序递推:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];
//int dfs(int x,int vv)
//{
//	if(mem[x][vv])
//		return mem[x][vv];
//	int sum=0;
//	if(x>n)
//	{
//		sum=0;
//	}else if(vv<v[x])
//	{
//		sum=dfs(x+1,vv);
//	}
//	else if(vv>=v[x])
//	{
//		sum=max(dfs(x+1,vv),dfs(x+1,vv-v[x])+w[x]);
//	}
//	mem[x][vv]=sum;
//	return sum;
//}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	f[0][0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;i<=m;i++)
		{
			if(j<v[i])//如果背包不够装
			{
				f[i][j]=f[i-1][j];
			}else if(j>=v[i])
			{
				f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			}
		}
	}
	cout<<f[n][m]<<endl;
	return 0;
}

记忆化搜索mem[N][N]数组的定义:从第x个物品开始(x~n),总体积<=j的最大价值。
正序递推,从1~x个物品,总体积<=j的最大价值。

空间优化:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010;
int n,m;
int v[N],w[N];
int f[N];
int g[N];
//int dfs(int x,int vv)
//{
//	if(mem[x][vv])
//		return mem[x][vv];
//	int sum=0;
//	if(x>n)
//	{
//		sum=0;
//	}else if(vv<v[x])
//	{
//		sum=dfs(x+1,vv);
//	}
//	else if(vv>=v[x])
//	{
//		sum=max(dfs(x+1,vv),dfs(x+1,vv-v[x])+w[x]);
//	}
//	mem[x][vv]=sum;
//	return sum;
//}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;i<=m;i++)
		{
			g[j]=f[j];
			if(j>=v[i])
			{
				g[j]=max(f[j],f[j-v[i]]+w[i]);
			}
		}
		memcpy(f,g,sizeof(f));
	}
	cout<<f[m]<<endl;
	return 0;
}

滚动数组??????

posted @ 2024-03-24 16:55  miao-jc  阅读(5)  评论(0编辑  收藏  举报