背上我可爱的小背包

题目内容为《背包九讲》内所提及的问题,全部由经典例题改编而来



A : 买零食

Time Limit:1.000 Sec Memory Limit:128 MiB

Description

贪吃的大三学长ZHB想要买一箱零食

已知他有一个箱子容量为\(V\)(正整数,\(0 \leqslant V \leqslant 200000\)),商店有\(n\)件零食(\(0<n\leqslant 30\),每件零食有一个体积(正整数))。

要求\(n\)件零食中,任取若干件装入箱内,ZHB学长买走零食后剩余空间最少。

Input

\(1\)个整数,表示箱子容量

\(1\)个整数,表示有\(n\)个物品

接下来\(n\)行,分别表示这\(n\)个物品的各自体积

Output

\(1\)个整数,表示箱子剩余空间。



Sample Input

24
6
8
3
12
7
9
7



Sample Output

0



PZ's Solution

1.经典的\(01\)背包,只是物品的价值与重量相等;

2.状态转移方程为\(f[j]=max(f[j],f[j-x]+x)\),其中\(x\)为零食的体积;

2.剩余空间即为\(V-f[V]\)

  • TAG:01背包



PZ.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int V,n,x,f[20005];
int main(){
	scanf("%d %d",&V,&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&x);
		for(int j=V;j>=x;--j)
			f[j]=max(f[j],f[j-x]+x);
	}
	printf("%d",V-f[V]);
	return 0;
}






B : 采药

Time Limit:1.000 Sec Memory Limit:128 MiB

Description

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是辰辰,你能完成这个任务吗?

Input

第一行有2个整数\(T(1 \leqslant T \leqslant 1000)\)\(M(1 \leqslant M \leqslant 100)\),用一个空格隔开,\(T\)代表总共能够用来采药的时间,\(M\)代表山洞里的草药的数目。
接下来的\(M\)行每行包括两个在\(1\)\(100\)之间(包括\(1\)\(100\))的整数,分别表示采摘某株草药的时间和这株草药的价值。

Output

\(1\)个整数,表示在规定的时间内可以采到的草药的最大总价值。



Sample Input

70 3
71 100
69 1
1 2



Sample Output

3



More Info

这是一道非常标准的\(01\)背包问题



PZ's Solution

1.\(01\)背包,状态转移方程为\(f[i]=max(f[i],f[i-t]+w)\)

  • TAG:01背包



PZ.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,M,t,w,f[1005];
int main(){
	scanf("%d %d",&T,&M);
	for(int i=1;i<=M;++i){
		scanf("%d %d",&t,&w);
		for(int j=T;j>=t;--j)
			f[j]=max(f[j],f[j-t]+w);
	}
	printf("%d",f[T]);
	return 0;
}






C : 燕山采药

Time Limit:1.000 Sec Memory Limit:128 MiB

Description

ZHB已经是一个老年人了,他经常需要一些中药补补身体,但无奈于空空的钱包,他只能自己到燕山上去采药。碰巧的是,他找到了一个山洞,山洞里全部都是草药。ZHB很想把它们全部采走,但受制于自己身体,他只能在有限的时间内去采。已知每一株药草都有对应的药效和采摘时间,请你设计一个算法帮助ZHB,让TA在有限的时间内采摘的药草药效总和最大。

Input

输入第一行有两个整数\(T(1 \leqslant T \leqslant 100000)\)\(M(1 \leqslant M \leqslant 10000)\),用一个空格隔开,\(T\)代表总共能够用来采药的时间,\(M\)代表山洞里的草药的数目。接下来的\(M\)行每行包括两个在\(1\)\(10000\)之间(包括\(1\)\(10000\))的整数,分别表示采摘某种草药的时间和这种草药的药效。

Output

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总药效。



Sample Input

70 3
71 100
69 1
1 2



Sample Output

140



More Info

本题基于采药改编,有些许不同,请注意



PZ's Solution

1.题面表达其实有些问题,但从样例应该能看出来,这是一个完全背包;

2.完全背包的状态转移方程与\(01\)背包相同,只是循环顺序的问题;

3.有状态转移方程\(f[i]=max(f[i],f[i-t]+w)\)

ps.不知道为啥C++的程序挂了,蜜汁问题~

  • TAG:完全背包



PZ.c

#include<stdio.h>
int T,M,t,w,f[100005];
int main(){
	scanf("%d %d",&T,&M);
	for(int i=1;i<=M;++i){
		scanf("%d %d",&t,&w);
		for(int j=t;j<=T;++j)
			if(f[j-t]+w>f[j]) f[j]=f[j-t]+w;
	}
	printf("%d",f[T]);
	return 0;
}






D : 卖书

Time Limit:1.000 Sec Memory Limit:128 MiB

Description

“卖书了,卖书了!亏本大甩卖啊!”

大书商小明非常想把手上的\(M\)本书卖给旗下\(N\)家加盟店。已知这些店收购这些书会给小明带来一定的利润,请你设计一个算法,帮助大书商小明获得最大盈利。

已知\(1\leqslant M\leqslant 15,1\leqslant N \leqslant 10\),小明可以任意数量的书卖给任意加盟店,但总数不超过小明持有的数\(M\)

Input

第一行有两个数,第一个数是小明持有的书本数\(N\),第二个数是加盟店店数\(M\)

接下来是一个\(N*M\)的矩阵,表明了第\(I\)个加盟店卖得\(J\)台书本的盈利。

Output

\(1\)行为最大盈利值

\(2\)到第\(n\)行为第\(x\)个加盟店分\(y\)本所产生的利润

并使输出的答案字典序最小



Sample Input

3 3
50 60 30
30 20 80
60 80 50



Sample Output

140
1 1
2 1
3 1



More Info

背包问题经典解法



Solution

题解借鉴自sycstudio的CJOJ 1131 机器分配 / Luogu 2066 机器分配 (动态规划)

1.背包问题,设\(f[i][j]\)代表前\(i\)个加盟店,总共卖出\(j\)本书获得的最大利润;

2.有状态转移方程\(f[i][j]=max(f[i][j],f[i-1][j-k]+a[i][k])\),其中\(a[i][k]\)代表第\(i\)个加盟店卖出\(k\)本书的利润;

3.之后难点在于输出字典序最小的方案(题面意思是要输出符合方案的\(x\)\(y\));

4.这里借鉴了其他博客的做法,使用了一个递归函数,倒退求出了字典序最小的解;

5.细节见代码;

  • TAG:动态规划;背包



std.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,a[20][20],f[20][20];
void printans(int N,int M){
	//N代表第N个加盟店,M代表还有M本书
	if(N==0) return;
	for(int i=0;i<=M;++i) //i代表这个加盟店拿走一部份书后,剩余的书本数
						  //这样,就能保证下一个加盟店拿的更少,保证了字典序方案
		if(f[N][M]==f[N-1][i]+a[N][M-i]){//如果当前的i可以满足最优的条件,说明这种方案符合答案
			printans(N-1,i);
			printf("%d %d\n",N,M-i);
			break;
		}
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			scanf("%d",&a[i][j]);
	for(int i=1;i<=n;++i)
		for(int j=0;j<=m;++j) //因为有的公司可以一本书不要,所有循环从0开始
			for(int k=0;k<=j;++k)
				f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]);
	printf("%d\n",f[n][m]);
	printans(n,m);//打印方案(注意从后往前递归)
	return 0;
}






E : 买零食(二)

Time Limit:1.000 Sec Memory Limit:128 MiB

Description

今天阳光正好,WWN学姐想去超市买几包零食送给正在敲代码的小萌新。

超市里的商品琳琅满目,大大小小价格不一的商品让热爱思考的学姐想到了一个有趣的问题:

他手上的袋子可以装下体积为\(V\)的零食,而每件商品都有自己所对应的价格和体积,那么她这次购物最大花销会是多少呢?

但是聪明的学姐很快就想出了答案,这让她感觉很没有挑战性...于是学姐又继续思考:

那么我这次购物第\(K\)大花销会是多少呢?

这次可让学姐把自己给难住了

那么就请你设计一个算法帮助学姐解决这个问题吧

Input

第一行包含一个整数\(T\),代表测试数据组数

对接下来的\(T\)组数据,每组数据有三行

第一行有三个整数\(N,V,K(N \leqslant 100 , V \leqslant 1000 , K \leqslant 30)\),分别代表零食数量、学姐手上袋子的体积和所要求的第\(K\)大花销

第二行有\(N\)个整数,分别代表每件零食的价格

第三行有\(N\)个整数,分别代表每件零食的体积

Output

\(T\)行整数,分别是每组数据的解(解的大小将小于\(2^{31}\)



Sample Input

3
5 10 2
1 2 3 4 5
5 4 3 2 1
5 10 12
1 2 3 4 5
5 4 3 2 1
5 10 16
1 2 3 4 5
5 4 3 2 1



Sample Output

12
2
0



More Info

可以参考背包九讲里面的思路,改自HDUOJ2639



Solution

思路借鉴了神回的HDU - 2639 Bone Collector II

1.如果只求最大,此题即为\(01\)背包的模板;

2.现在需求第\(K\)大,则设\(f[i][k]\)为体积为\(i\)时的第\(k\)大解;

3.按照原本的状态转移方程\(f[i]=max(f[i],f[i-v[j]]+w[j])\),只需要求出当前\(i\)体积的所有可能情况,并将这些情况的值进行排序,将前\(k\)大(不重复)的值装入相应的\(f[i][k]\)即可;

ps.C++又蜜汁挂了,这里直接写了一个快排代替排序;

  • TAG:01背包



std.c

#include<stdio.h>
int T,n,v,k,w[5005],V[5005],F[1005][50],f[5005],cnt,fcnt;
void quicksort(int l,int r)
{
    int i=l,j=r,mid=f[(i+j)/2];
    while(i<j)
    {
        while(f[j]<mid) j--;
        while(f[i]>mid) i++;
        if(i<=j)
        {
            int t=f[i];
            f[i]=f[j];
            f[j]=t;
            i++;
            j--;
        }
    }
    if(l<j) quicksort(l,j);
    if(i<r) quicksort(i,r);
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d %d %d",&n,&v,&k);
		for(int i=1;i<=n;++i)
			scanf("%d",&w[i]);
		for(int i=1;i<=n;++i)
			scanf("%d",&V[i]);
		for(int i=0;i<=v;++i)
			for(int j=0;j<=k;++j)
				F[i][j]=0;
		for(int i=1;i<=n;++i){
			for(int j=v;j>=V[i];--j){
				fcnt=cnt=0;
				for(int K=1;K<=k;++K){
					f[++cnt]=F[j][K];
					f[++cnt]=F[j-V[i]][K]+w[i];
				}
                //求出所有可能的情况
				quicksort(1,cnt);//从大到小排序
				for(int K=1;K<=cnt;++K){
					if(fcnt==k) break;
					if(f[K]==f[K+1]) continue;
					F[j][++fcnt]=f[K];
				}
               	//将不重复的前k大的值装入相应的F[j][K]即可
			}
		}
		printf("%d\n",F[v][k]);
	}
	return 0;
}
posted @ 2021-02-04 00:02  PotremZ  阅读(112)  评论(0编辑  收藏  举报