dp动态规划 背包问题
01背包(每个物品就1个)
理解:
动态转移方程:
dp[i][j]=max(dp[i-1][j] , dp[i-1][ j-p[i] ]+m[i]);
代码:
#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main()
{
int n,x;
while(~scanf("%d %d",&n,&x))
{
int p[n+1],m[n+1];
int i,j;
int dp[n+1][x+1]={{0}};
for(i=1;i<=n;i++)
{
cin>>p[i]>>m[i];
}
for(i=0;i<=x;i++)
{
dp[0][i]=0;
}
for(i=1;i<=n;i++)
{
for(j=1;j<=x;j++)//j表示当前背包容量
{
if(p[i]> j)//如果该物品容量大于当前背包容量
{
dp[i][j]=dp[i-1][j];
}
else
{
dp[i][j]=max(dp[i-1][j] , dp[i-1][ j-p[i] ]+m[i]);
}
}
}
cout<<dp[n][x]<<endl;
}
return 0;
}
改进
因为每层都只于上层有关,故压缩数组,
又因为从小到大会覆盖到上层数据,故从小到大
更新数据
优化后:
#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main()
{
int n,x;
while(~scanf("%d %d",&n,&x))
{
int p[n+1],m[n+1];
int i,j;
int dp[x+1]= {0};
for(i=1; i<=n; i++)
{
cin>>p[i]>>m[i];
}
for(i=0; i<=x; i++)
{
dp[i]=0;
}
for(i=1; i<=n; i++)
{
for(j=x; j>=1; j--) //j表示当前背包容量
{
if(j>=p[i])
{
dp[j]=max(dp[j], dp[j-p[i] ]+m[i]);
}
}
}
cout<<dp[x]<<endl;
}
return 0;
}
完全背包问题(物品个数无限)
朴素算法
只比01背包多了一层更新放置几个物品的循环
#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main()
{
int n,x;
while(~scanf("%d %d",&n,&x))
{
int p[n+1],m[n+1];
int i,j;
int dp[x+1]= {0};
for(i=1; i<=n; i++)
{
cin>>p[i]>>m[i];
}
for(i=0; i<=x; i++)
{
dp[i]=0;
}
for(i=1; i<=n; i++)
{
for(j=x; j>=1; j--) //j表示当前背包容量
{
//*****************只多这些
for(int k=0;k<=j/p[i];k++)
//只多这些*****************
dp[j]=max(dp[j],dp[ j- k*p[i] ]+k*m[i]);
}
}
cout<<dp[x]<<endl;
}
return 0;
}
改进中间态
动态转移方程:
dp[i][j]=max(dp[i-1][j] , dp[ i ] [ j-p[i] ]+m[i]);
与01区别:后面为dp[i]行
改进
由改进中间态得:只需要把01改进算法,改为从小到大推即可
#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main()
{
int n,x;
while(~scanf("%d %d",&n,&x))
{
int p[n+1],m[n+1];
int i,j;
int dp[x+1]= {0};
for(i=1; i<=n; i++)
{
cin>>p[i]>>m[i];
}
for(i=0; i<=x; i++)
{
dp[i]=0;
}
for(i=1; i<=n; i++)
{
for(j=p[i]; j<=m; j++)//从可以放入一个p[i]开始处理
//j表示当前背包容量
{
dp[j]=max(dp[j],dp[ j- p[i] ]+m[i]);
}
}
cout<<dp[x]<<endl;
}
return 0;
}
多重背包(物品个数有限)
两个条件:
1.和完全背包一样
2.数量个数有限
朴素算法
代码思想:从01背包的状态转移方程式,去增加第i个物品拿k个的循环
#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main()
{
int p[501];//价格
int v[501];//价值
int s[501];//数量
int dp[6100];
int n,m;
cin>>n>>m;
int i,j,k;
for(i=1;i<=n;i++)
{
cin>>p[i]>>v[i]>>s[i];
}
for(i=1;i<=n;i++)
{
for(j=m;j>=1;j--)
{
for(k=0;k<=s[i]&&j>=k*p[i];k++)
{
//从01背包的状态转移方程式,去增加第i个物品拿k个的循环
dp[j]=
max(dp[j],dp[j-k*p[i]] +k*v[i] );
}
}
}
cout<<dp[n];
return 0;
}
改进:利用二进制
没学过二进制, 就不写了
二进制思想:
假设有 1000 个苹果,现在要取n个苹果,如何取?朴素的做法应该是将苹果一个一个拿出来,直到n个苹果被取出来。
再假设有 1000 个苹果和10只箱子,利用箱子进行某些预工作,然后如何快速的取出n个苹果呢?So..可以在每个箱子中放 2^i (i<=0<=n)个苹果,也就是 1、2、4、8、16、32、64、128、256、489(n减完之前的数之后不足 2^i,取最后剩余的数),相当于把十进制的数用二进制来表示,取任意n个苹果时,只要推出几只箱子就可以了。
基于这种思想把一种多件物品转换为,多件一种物品,然后用01背包求解即可。
二维费用背包
解法:多加一维就可
const int maxn=1e4+5;
int n,x,y,w[N+1],v[N+1],g[N+1];
int dp[X+1][Y+1];
int main()
{
cin>>n>>x>>y;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i]>>g[i];
for(int i=1;i<=n;i++)
{
for(int j=x;j>=w[i];j--)
{
for(int k=y;k>=v[i];k--)
{
dp[j][k]=max(dp[j][k],dp[j-w[i]][k-v[i]]+g[i]);
}
}
}
cout<<dp[x][y]<<endl;
return 0;
}
实战1.sdut- 最少硬币问题
#include <iostream>
#include <cstdio>
#include <cstring>///包含memset()
#define MAX 0x3f3f3f3f ///定义最大值MAX
using namespace std;
int min(int, int);
int T[15];
//T[i]表示 第i种硬币的面值
int coins[15];
//coins[i]表示第i种硬币的个数
long long int dp[20011];
//dp[目标面额] 表示达到当前面额需要最少的硬币数量
int main()
{
int n, m;
scanf("%d", &n);
for(int i = 0; i < n; i++)
cin>>T[i]>>coins[i];
scanf("%d", &m);
memset(dp, MAX, sizeof(dp));//初始化为最大值
dp[0] = 0;//边界条件,当目标面额是0时,需要0个硬币
for(int i = 0; i < n; i++)
{//i是第i种硬币,遍历每一种硬币
for(int j = 1; j <= coins[i]; j++)
{//j是硬币个数,每一种硬币有coins[i]个,对第i种硬币的每一个硬币都遍历
for(int k = m; k >= T[i]; k--)
{//k是当前面额,当需要找回的面额 大于等于 第i种硬币的面值
//判断已保存的dp[当前面额]的值 和 dp[替换掉当前面额]的值 的大小
dp[k] = min(dp[k], dp[k - T[i]] + 1);
}
}
}
printf("%lld\n", dp[m] < MAX ? dp[m] :-1);
return 0;
}