写在前面:
这些背包问题都是01背包或其衍生体。难度为易,专门为初学背包问题提供的。
前置知识:背包九讲中的01背包问题
题单链接:背包问题(简单)
文章目录
1、P1048 采药
1、P1048 采药
01背包模板题
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 110,M = 1010;
int n,m;
int v[N],w[N];
int f[N][M];
int main(){
cin >> m >> n;
for(int i = 1; i <= n; i ++ ){
cin >> v[i] >> w[i];
}
for(int i = 1; i <= n; i ++ ){
for(int j = 1; j <= m; j ++ ){
if(j < v[i]) f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
空间优化到O(M)后:
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 110,M = 1010;
int n,m;
int v[N],w[N];
int f[M];
int main(){
cin >> m >> n;
for(int i = 1; i <= n; i ++ ){
cin >> v[i] >> w[i];
}
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;
}
2、P1164 小A点菜
2、P1164 小A点菜
01背包求方案数模板题
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 110,M = 1e4 + 10;
int n,m;
int v[N];
int f[M];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> v[i];
f[0] = 1;
for(int i = 1; i <= n; i ++ ){
for(int j = m; j >= v[i]; j --){
f[j] += f[j - v[i]];
}
}
cout<<f[m];
return 0;
}
3、P1049 装箱问题
- 空间即为体积也为价值。为了使得剩余空间最小,需要箱子内的物品体积越大。
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 40,M = 2e4 + 10;
int n,m;
int v[N],w[N];
int f[M];
int main(){
cin >> m >> n;
for(int i = 1; i <= n; i ++ ) cin >> v[i];
for(int i = 1; i <= n; i ++ ){
for(int j = m; j >= v[i]; j --){
f[j] = max(f[j],f[j - v[i]] + v[i]);
}
}
cout<<m-f[m]<<endl;
return 0;
}
4、P1060 开心的金明
4、P1060 开心的金明
价值为v[i]*w[i]
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define x first
#define y second
#define mm(a,x) memset(a,x,sizeof a)
#define endl "\n"
const int N = 3e4 + 10;
int n,m;
int w[N],v[N];
int f[N];
int main() {
cin >> m >> n;
for(int i = 0; i < n; i ++ ){
cin >> w[i] >> v[i];
v[i] *= w[i];
}
for(int i = 0; i < n; i ++ ){
for(int j = m; j >= w[i]; j --){
f[j] = max(f[j],f[j - w[i]] + v[i]);
}
}
cout<<f[m];
return 0;
}
5、P1510 精卫填海
- 填平之后剩下最大体力转换为求消耗最小体力且能填平
- 从小开始枚举消耗体力值直到不小于填平体积
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 1e4 + 10,M = 1e4 + 10;
int maxv,n,m;
int v[N],w[N];
int f[M];
int main(){
cin >> maxv >> n >> m;
for(int i = 1; i <= n; i ++ ){
cin >> w[i] >> v[i];
}
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]);
}
}
if(f[m] < maxv){
puts("Impossible");
}else{
int k = 0;
while(f[k] < maxv) k ++;
printf("%d",m - k);
}
return 0;
}
6、P1926 小书童——刷题大军
数据范围较小,可以暴搜
方法一:暴搜
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200;
int n,m,k,r;
int s[N],v[N],w[N];
int maxv;
void dfs(int u,int a,int b){ //a:时间,b:分数
if(maxv >= r - a) return ;
if(b >= k) maxv = r - a;
if(u >= m) return ;
for(int i = u + 1; i <= m; i ++ ){
dfs(i,a + v[i],b + w[i]);
}
}
int main(){
cin >> n >> m >> k >> r;
for(int i = 1; i <= n; i ++ ) cin >> s[i];
for(int i = 1; i <= m; i ++ ) cin >> v[i];
for(int i = 1; i <= m; i ++ ) cin >> w[i];
dfs(0,0,0);
sort(s + 1,s + n + 1);
int ans = 0;
for(int i = 1; i <= n; i ++ ){
if(maxv >= s[i]) maxv -= s[i],ans += 1;
}
cout<<ans;
return 0;
}
方法二:01背包
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200;
int n,m,k,r;
int s[N],v[N],w[N];
int f[N];
int main(){
cin >> n >> m >> k >> r;
for(int i = 1; i <= n; i ++ ) cin >> s[i];
sort(s,s+n);
for(int i = 1; i <= n; i ++ ) s[i] += s[i - 1];
for(int i = 1; i <= m; i ++ ) cin >> v[i];
for(int i = 1; i <= m; i ++ ) cin >> w[i];
for(int i = 1; i <= m; i ++ ){
for(int j = r; j >= v[i]; j --){
f[j] = max(f[j],f[j - v[i]] + w[i]);
}
}
int c = 0;
while(f[c] < k) c ++;
int ans = r - c;
int i = n;
while(ans < s[i]) i--;
cout<<i;
return 0;
}
7、P1802 5倍经验日
- 这道题细节是输了也有经验吃。
- 输了就是j<user[i],把这些都加上lose[i]
- 其他都可以输也可以赢,0也要带上
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;
int n,m;
LL lose[N],win[N];
int v[N];
LL f[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> lose[i] >> win[i] >> v[i];
for(int i = 1; i <= n; i ++ ){
for(int j = m; j >= v[i]; j --){
f[j] = max(f[j] + lose[i],f[j - v[i]] + win[i]);
}
for(int j = v[i] - 1; j >= 0; j --){
f[j] += lose[i];
}
}
cout<<f[m]*5;
return 0;
}
8、P1734 最大约数和
- 先预处理出1~S每个数的约数和
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;
int n;
int v[N];
int f[N];
int calc(int n){
int s = 0;
for(int i = 1; i <= n/i; i ++ ){
if(n % i == 0){
s += i;
if(i != n/i) s += n/i;
}
}
s-=n;
return s;
}
int main(){
cin >> n;
for(int i = 1; i <= n; i ++ ) v[i] = calc(i);
for(int i = 1; i <= n; i ++ ){
for(int j = n; j >= i; j --){
f[j] = max(f[j],f[j - i] + v[i]);
}
}
cout<<f[n];
return 0;
}
9、P2392 kkksc03考前临时抱佛脚
数据较小,可以暴搜
方法一:暴搜
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 30;
int s1[N],s[N][N];
int minv;
int ans;
void dfs(int u,int k,int l,int r){
if(k == s1[u] + 1){
minv = min(minv,max(l,r));
return ;
}
l += s[u][k];
dfs(u,k + 1,l,r);
l -= s[u][k];
r += s[u][k];
dfs(u,k + 1,l,r);
r -= s[u][k];
}
int main(){
for(int i = 1; i <= 4; i ++ ) cin >> s1[i];
for(int i = 1; i <= 4; i ++ ){
for(int j = 1; j <= s1[i]; j ++ ){
cin >> s[i][j];
}
}
for(int i = 1; i <= 4; i ++){
minv = 1e9;
dfs(i,1,0,0);
ans += minv;
}
cout<<ans;
return 0;
}
方法二:01背包
- 找出每道题左脑能最接近一半时间,用总时间减去它得到最优时间(即max(t1,t2))
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e4 + 10;
int s1[N],s[N][N];
int ans;
int f[N],sum[N];
int main(){
for(int i = 1; i <= 4; i ++ ) cin >> s1[i];
for(int i = 1; i <= 4; i ++ ){
for(int j = 1; j <= s1[i]; j ++ ){
cin >> s[i][j],sum[i] += s[i][j];
}
}
for(int i = 1; i <= 4; i ++){
memset(f,0,sizeof f);
for(int j = 1; j <= s1[i]; j ++ ){
for(int k = sum[i]/2; k >= s[i][j]; k --) f[k] = max(f[k],f[k - s[i][j]] + s[i][j]);
}
ans += sum[i] - f[sum[i]/2];
}
cout<<ans;
return 0;
}
10、P1466 [USACO2.2]集合 Subset Sums
10、P1466 [USACO2.2]集合 Subset Sums
- 如果和为奇数,不可能划分
- 求出能达到一半集合总和的总方案数,最后除2去重
暴搜骗分
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 50;
int n;
int ans;
int s;
int dfs(int u,int l,int r){
if(l > s/2 || r > s/2) return 0;
if(u == 0){
if(l == r) return 1;
return 0;
}
return dfs(u - 1,l + u,r) + dfs(u - 1,l,r + u);
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
s = (n*(n+1))/2;
if(s % 2 == 1) cout<<0;
else{
ans = dfs(n,0,0);
cout<<ans/2;
}
return 0;
}
01背包
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 3e3 + 10;
int n;
LL f[N];
int s;
int main(){
ios::sync_with_stdio(false);
cin >> n;
s = (n*(n+1))/2;
if(s % 2 == 1){
cout<<0;
}else{
f[0] = 1;
for(int i = 1; i <= n; i ++ ){
for(int j = s/2; j >= i; j --){
f[j] += f[j - i];
}
}
cout<<f[s/2]/2;
}
return 0;
}
11、P2370 yyy2015c01 的 U 盘
- 找出最小使用的接口,这就需要从所有可能的接口大小中找,枚举效率低,这里采用二分答案
- 剩下的就交给背包
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;
int n,p,m;
int v[N],w[N];
int f[N];
bool check(int x){
memset(f,0,sizeof f);
for(int i = 1; i <= n; i ++ ){
if(v[i]<=x)
for(int j = m; j >= v[i]; j --){
f[j] = max(f[j],f[j - v[i]] + w[i]);
}
}
return f[m] >= p;
}
int main(){
ios::sync_with_stdio(false);
cin >> n >> p >> m;
int l = 0,r = 0;
for(int i = 1; i <= n; i ++ ){
cin >> v[i] >> w[i];
r = max(r,v[i]);
}
bool flag = true;
while(l < r){
int mid = l + r >> 1;
if(check(mid)) r = mid,flag = false;
else l = mid + 1;
}
if(flag) puts("No Solution!");
else cout<<l<<endl;
return 0;
}
12、CF294B Shaass and Bookshelf
12、CF294B Shaass and Bookshelf
这道题就特又意思,其中这句很关键
The sum of the widths of the horizontal books must be no more than the total thickness of the vertical books.
水平放置的书的宽度和不能多于竖直放置的书的总厚度。
- 分两类,一类取上面,一类在下面。
- 题目要让求出最小的长度:因此,要让下面那类最小,即下面的书组合成的厚度和最小。
- 厚度总和是一定的,那就让上面的厚度和最大,并且上面的宽度一定要是小的
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;
int n,m;
int t[N],w[N];
int f[N];
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i ++){
cin >> t[i] >> w[i];
m += t[i];
}
memset(f,0x3f,sizeof f);
f[0] = 0;
for(int i = 1; i <= n; i ++ ){
for(int j = m; j >= t[i]; j --){
f[j] = min(f[j],f[j - t[i]] + w[i]);
}
}
int k = m;
while(k > 0 && f[k] > m - k) k --;
cout<<m-k;
return 0;
}
13、CF19B Checkout Assistant
- 把某商品从推车上偷走需要1秒,不偷走需要t[i] + 1秒(额外1秒用来偷其他商品)
- 要找到最小金额,因此初始化f[0] = 0,其他为极大
- 花费时间至少为n秒,在所有可能花费中找到最小金额。
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e4 + 10;
LL n,m;
LL t[N],c[N];
LL f[N];
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i ++ ){
cin >> t[i] >> c[i];
t[i] += 1;
m = max(m,t[i]);
}
m += n;
memset(f,0x3f,sizeof f);
f[0] = 0;
for(int i = 1; i <= n; i ++ ){
for(int j = m; j >= t[i]; j --){
f[j] = min(f[j],f[j - t[i]] + c[i]);
}
}
LL ans = 1e18;
for(int i = n; i <= m; i ++ ) ans = min(ans,f[i]);
cout<<ans;
return 0;
}
14、P4141 消失之物
f[i][0]:不丢掉i的所有方案数
f[i][1]:丢掉i的所有方案数
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define x first
#define y second
#define mm(a,x) memset(a,x,sizeof a)
#define endl "\n"
#define inf 0x3f3f3f3f
typedef long long ll;
const int N = 1e4 + 10;
int n,m;
int v[N];
int f[N][2];
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> v[i];
f[0][0] = f[0][1] = 1;
for(int i = 1; i <= n; i ++ ){
for(int j = m; j >= v[i]; j --){
f[j][0] += f[j - v[i]][0];
f[j][0] %= 10;
}
}
for(int i = 1; i <= n; i ++ ){
for(int j = 1; j <= m; j ++ ){
if(j >= v[i]) f[j][1] = (f[j][0] - f[j - v[i]][1] + 10) % 10;
else f[j][1] = f[j][0]%10;
cout<<f[j][1];
}
puts("");
}
return 0;
}