在这一篇文章之中,我们来了解一下什么是动态规划。
了解了动态规划的基本概念,我们来了解一下状态转移方程
动态规划的所有题目都是在此基础上加入其他的元素来进行编写代码的。一定要记住
而现在,我们来仔细地将在动态规划的背包问题来讲一讲。
动态规划之背包问题
首先我们要明白背包问题是一个动态规划的分支,所以我们的动态规划的动态转移方程还是照例要用的。从之前的学习可以知道,
d[j]=max(d[j-1],d[j-1]+w[i);
这是动态规划的基本动态转移方程,下面我们来讲讲每个背包问题的分支
一:完全背包
完全背包的理解:就是每一个物体可以使用无限多次
所以在完全背包问题的动态转移方程时,所处的循环要正向循环。
话不多说,放代码
#include<bits/stdc++.h>
using namespace std;
int d[100100],c,wi,vi,n,i,j;
int main()
{
cin>>c>>n;
for(int i=1;i<=n;i++)
{
cin>>wi>>vi;
for(int j=wi;j<=c;j++)
{
d[j]=max(d[j],d[j-wi]+vi);
}
}
cout<<d[c];
return 0;
}
二:多重背包
这里的不同就是每个物体都有限定的次数
比如说
很明显,这就是我们的多重背包
我们直接看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
int main()
{
cin >> n >> m;
int cnt = 0;
for (int i = 1; i <= n; i ++ )
{
int a, b, s;
cin >> a >> b >> s;
int k = 1;
while (k <= s)
{
cnt ++ ;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
if (s > 0)
{
cnt ++ ;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
可以看到,此时动态规划的动态转移方程的循环已经从正序变成了倒序。
那么接下来就是我们的最后一个基础背包分类
三:01背包
顾名思义,这就是每种物品只能使用一次
看题目好吧
#include<bits/stdc++.h>
using namespace std;
long long n,m,v,dp[10010];
int main(){
cin>>n>>m;
dp[0]=1;
for(int i=1;i<=n;i++)
{
cin>>v;
for(int j=m;j>=v;j--)
{
dp[j]=dp[j]+dp[j-v];
}
}
cout<<dp[m];
return 0;
}
四:背包进阶
学习了基本的三种背包以后,我们来挑战以下各种背包的融合;
首先:二维背包
#include<bits/stdc++.h>
using namespace std;
int dp[410][410];
int n,v,w,c;
int maxv,maxw;
int main(){
cin>>maxv>>maxw;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>v>>w>>c;
for(int j=maxv;j>=v;j--)
{
for(int k=maxw;k>=w;k--)
{
dp[j][k]=max(dp[j][k],dp[j-v][k-w]+c);
}
}
}
cout<<dp[maxv][maxw];
return 0;
}
我们看一下这里的动态转移方程
dp[j][k]=max(dp[j][k],dp[j-v][k-w]+c);
可以看到,这里的动态转移方程已经从一维变成了二维,这就可以视为这是普通的背包问题的升级版,以后的做题是不太可能是只有一种的知识点的,难度会提高。当然百度会给你做题,咳咳,言归正传,我们要熟读每一种的知识点,避免理论全会,实战就废的情况。
二:“混合”背包
什么是混合背包呢?很简单,理论上来说就是三种背包的融合。然后在编写的过程中做一些减少内存和时间运行的方法,就可以了。
我们先来看一道题目:
可以看到,这里是用的是三种背包的融合,
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int v, w, s;
cin >> v >> w >> s;
if (!s)
{
for (int j = v; j <= m; j++)
f[j] = max(f[j], f[j - v] + w);
}
else
{
if (s == -1)
s = 1;
for (int k = 1; k <= s; k *= 2)
{
for (int j = m; j >= k * v; j--)
f[j] = max(f[j], f[j - k * v] + k * w);
s -= k;
}
if (s)
{
for (int j = m; j >= s * v; j--)
f[j] = max(f[j], f[j - s * v] + s * w);
}
}
}
cout << f[m] << endl;
return 0;
}
可以看到,这里已经把每个部分都分开了。因为多重背包和01背包的循环结构都比较相似。所以我在第一次分类的时候先把它们分为一类。后面再把他们再次分类就行了;
三:背包+并查集
在此类的及进阶背包之中,我们要使用并查集的知识,在这里i简单的说一下:
并查集(简单版)
首先并查集的原理是“树”,并查集一般用来查找和合并。查找就是判断这一个数所在的树的根节点是谁?与另一个数所在的树的根节点是不是同一个,如果不是,则进行一个合并。
在这里提供一下并查集的简单代码。就是你基本写关于并查集必须要做的这个代码啊
int find(int x)
{
if(x==f[x])
return x;
else
return f[x]=find(f[x]);
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
f[fx]=fy ;
}
那我们就来正式的———————————————————————————————————
编写代码
首先我们来看一下题目:
可以看到,这里必须要把每个树的所有物品都要进行购买,那我们何不在把他们合并后把所有的价钱和价值堆在根节点上,在把除了根节点以外的数给清空了(也就是变为0)。
所以我们接下来来看看代码啊:
#include<bits/stdc++.h>
using namespace std;
int n,m,maxn;
int w[10005],v[10005],dp[10005];
int f[10005];
int find(int x)
{
if(x==f[x])
return x;
else
return f[x]=find(f[x]);
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
f[fx]=fy ;
}
int main()
{
cin>>n>>m>>maxn;
for(int i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
f[i]=i;
}
int x,y;
for(int i=1;i<=m;i++)
{
cin>>x>>y;
merge(x,y);
}
int root;
for(int i=1;i<=n;i++)
{
if(f[i]!=i)
{
root=find(i);
w[root]=w[root]+w[i];
v[root]=v[root]+v[i];
w[i]=0;
v[i]=0;
}
}
for(int i=1;i<=n;i++)
{
if(w[i]==0&&v[i]==0)
{
continue;
}
for(int j=maxn;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i])
;}
}
cout<<dp[maxn];
return 0;
}
这就是我们的一个代码实现了。
四 某些背包问题要写初始值!!
在写动态转移方程的时候,不难发现它是一个小递归的方式进行运算的。所以在某些特殊的题目之中会有不定义初始值就死循环的情况啊。下面我们来看一道题目
在这个题目中,我们仔细观察可以知道,一本书都不买也是一种方案。所以我们在定义初始值的时候要
dp[0]=1;
所以在接下来的循环后,后面的都可以正常运行了
看一下完整的代码
#include<bits/stdc++.h>
using namespace std;
long long n,m,v,dp[10010];
int main(){
cin>>n>>m;
dp[0]=1;
for(int i=1;i<=n;i++)
{
cin>>v;
for(int j=m;j>=v;j--)
{
dp[j]=dp[j]+dp[j-v];
}
}
cout<<dp[m];
return 0;
}