首先分享一下我学习背包问题时遇到的好文章dd大牛的背包九讲这个文章注重思路,入门必备
目录
第一道题:疯狂的采药
P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
这是一道很简单的完全背包的模板题,只需要记住模板即可,如果想要知道相应的原理可以去看看上面分享的俩篇文章,但是要注意一点的是本题最多有 10^7的 时间,每种草药的价值最大是 10^4,所以极限情况下价值总和是 10^7*10^4=10^11,会爆 int,所以要开 long long。
//完全背包的板子
for (int i = 1; i <= m; i++)//不同的物品
{
for (int j = t[i]; j <= T; j++)//时间从小到大
{
f[j] = max(f[j], f[j - t[i]] + v[i]);
}
}
//01背包的板子
for (int i = 1; i <= m; i++)//不同的物品
{
for (int j = T; j >=t[i]; j--)//时间从大到小
{
f[j] = max(f[j], f[j - t[i]] + v[i]);
}
}
代码:
#include<bits/stdc++.h>
using namespace std;
long long t[10005000], f[10005000], v[10005];
int main()
{
long long T, m;
cin >> T >> m;
for (int i = 1; i <= m; i++)
{
cin >> t[i] >> v[i];
}
for (int i = 1; i <= m; i++)
{
for (int j = t[i]; j <= T; j++)
{
f[j] = max(f[j], f[j - t[i]] + v[i]);
}
}
cout << f[T] << endl;
return 0;
}
第二道题:樱花
P1833 樱花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
其实是一道混合背包的题,有俩种方法,一种是暴力法,对于大于1的花直接枚举出来看一次到看n次的情况看取不取,而还有一种方法则是二进制拆分,同时对于完全背包也有俩种做法,第一种是将其看作一个很大的数比如10000,再按照上面的方法写,还有一种则是进行分类,对其单独采用完全背包的处理方式。
代码:
多重背包简单的解法
#include<bits/stdc++.h>
using namespace std;
int N, V;
int f[2020];
int main()
{
int h1, h2, m1, m2;
char c;
cin >> h1 >> c >> m1 >> h2 >> c >> m2;
m = (h2 - h1) * 60 + m2 - m1;
cin >> N ;
for (int i = 1; i <= N; i++)
{
int v, w, s;
cin >> v >> w >> s;
for (int j = V;j >= v; j--)
{
for (int k = 1; k <= s && k * v <= j; k++)
{
f[j] = max(f[j], f[j - k * v] + k * w);
}
}
}
cout << f[V] << endl;
return 0;
}
混合背包问题二进制
#include<bits/stdc++.h>
using namespace std;
int n, m;
int f[10005];
int main()
{
vector<pair<int, int>>goods;//first为体积second为价值
int h1, h2, m1, m2;
char c;
cin >> h1 >> c >> m1 >> h2 >> c >> m2;
m = (h2 - h1) * 60 + m2 - m1;
cin >> n;
for (int i = 0; i < n; i++)
{
int v, w, s;
cin >> v >> w >> s;
if (s == 0)s = 10000;
for (int j = 1; j <= s; j *= 2)
{
s -= j;
goods.push_back({ j * v,j * w });
}
if (s > 0)
{
goods.push_back({ s * v,s * w });
}
}
for (int i = 0; i < goods.size(); i++)
{
for (int j = m; j >= goods[i].first; j--)
{
f[j] = max(f[j], f[j - goods[i].first] + goods[i].second);
}
}
cout << f[m] << endl;
return 0;
}
混合背包分类二进制
#include<bits/stdc++.h>
using namespace std;
int n, m;
int f[2020];
struct thing
{
int kind;
int v, m;
};
vector<thing>things;
int main()
{
int h1, h2, m1, m2;
char c;
cin >> h1 >> c >> m1 >> h2 >> c >> m2;
m = (h2 - h1) * 60 + m2 - m1;
cin >> n;
for (int i = 0; i < n; i++)
{
int v, w, s;
cin >> v >> w >> s;
if (s == 0)things.push_back({ 0,v,w });
else
{
for (int j = 1; j <= s; j *= 2)
{
s -= j;
things.push_back({1, v * j, w * j});
}
if (s > 0)
{
things.push_back({ 1,v * s,w * s });
}
}
}
for (auto thing : things)//遍历
{
if (thing.kind == 0)//完全背包
{
for (int i = thing.v; i <= m; i++)
{
f[i] = max(f[i], f[i - thing.v] + thing.m);
}
}
else//01背包
{
for (int i = m; i >= thing.v; i--)
{
f[i] = max(f[i], f[i - thing.v] + thing.m);
}
}
}
cout << f[m] << endl;
return 0;
}
第三道题:摆花
P1077 [NOIP2012 普及组] 摆花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
这道题的代码其实并不难,只要理清如何摆花就可以了,首先,我们可以根据花的次序依次来选是摆1个还是到a(i)(a(i)表示第i种花最多摆多少次),且注意判断到后面应该是剩余的空间v和a(i)的较小值为第i个最多摆几次的上限,并且我们可以很容易写出二维的状态转移方程,f[i][j]表明在第i种花,且已经占用j个位置时的方案数
f[i][j]=f[i-1][j](表明当不放第i种的情况的方案数)+f[i-1][j-k](表明放第i种花的情况且放置的数量为k时的方案数)
根据二维的很容易得到答案为f[n][m],再用滚动数组优化一下就可以得到答案了,同时注意初始化
f[0][0]应该是等于1
代码:
#include<bits/stdc++.h>//滚动数组状态转移方程为f[j]=(f[j]+f[j-k])前面的f[j]为f[i][j]后面的f[j]为f[i-1][j]一开是不取第i个,后面是挨个取完第i个,俩者之和为f[i][j]
using namespace std;
int a[105], f[105];
int mod = 1000007;
int main()
{
int n, m;
cin >> n >> m;
f[0] = 1;
for (int i = 1; i <= n; i++)
{
int a;
cin >> a;
for (int j = m; j > 0; j--)
{
for (int k = 1; k <= min(a, j); k++)
{
f[j] = (f[j] + f[j - k]) % mod;
}
}
}
cout << f[m];
return 0;
}
第四道题:金明的预算方案
P1064 [NOIP2006 提高组] 金明的预算方案 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
这看似是个有依赖的背包问题,而实际上可以转化为多几种情况的01背包问题,主要是一共就5钟情况①不买主件 ②买主件 ③买主件+副件1 ④买主件+副件2 ⑤买主件+副件1+副件2,然后注意判断是否能够买下的边界条件判断就可以了
代码:
#include<iostream>
using namespace std;
const int N = 32000;
int m, n, mw[N], mv[N], fw[N][3], fv[N][3], f[N], v, p, q;//mw主件重量,mv主件价值,fw主件对应的附件重量,fv主副价值,n总重量,m总个数
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> v >> p >> q;
if (!q) {//如果是主件
mw[i] = v;//主件重量
mv[i] = v * p;//主件价值与重量乘积
}
else {//如果是附件
fw[q][0]++;//用来记录是第q个主件的第几个附件
fw[q][fw[q][0]] = v;
fv[q][fw[q][0]] = v * p;
}
}
for (int i = 1; i <= m; i++)
for (int j = n; j >= mw[i]; j--) {//01背包模板
//每一个if的前提是背包能不能装下该物品
//情况1:只要主件 和啥都不要比较
f[j] = max(f[j], f[j - mw[i]] + mv[i]);
//情况2:主件和附件1 和上面选出的较大值比较
if (j >= mw[i] + fw[i][1])f[j] = max(f[j], f[j - mw[i] - fw[i][1]] + mv[i] + fv[i][1]);
//情况3:主件和附件2 和上面选出的较大值比较
if (j >= mw[i] + fw[i][2])f[j] = max(f[j], f[j - mw[i] - fw[i][2]] + mv[i] + fv[i][2]);
//情况4:都要
if (j >= mw[i] + fw[i][1] + fw[i][2])
f[j] = max(f[j], f[j - mw[i] - fw[i][1] - fw[i][2]] + mv[i] + fv[i][1] + fv[i][2]);
}//因为开在全局区则初值为0,所以就算没有三个附件,有一个或者俩个是空的也不影响最后的结果
//输出在价值为n时能得到的最大值
cout << f[n] << endl;
return 0;