01背包
拔河
题目描述
输入
每组输入的第一行是一个正整数n(2<=n<=100),表示共有n个人。
接下来n行,每行输入一个整数w(1<=w<=450),表示每个人的体重。
输出
样例输入
3 100 90 200
样例输出
190 200
题目要求两个队伍的体重之和尽可能相近,假设所有队员的总质量为sum,那么两个队伍的质量分别约为:sum/2。那么现在我们用01背包来解决这个问题。如果背包的大小是sum/2,那么在可供选择的队员中选择,使得选择的队员的总质量不超过sum/2,那么这样就可以使得所选择的队员的总质量达到最接近sum/2,间接的,剩下的所有队员分给另外一个队伍。这样两个队员之间的质量差就最小了。
1 #include <cstdio> 2 #include <algorithm> 3 4 using namespace std; 5 6 int main() 7 { 8 int n; 9 while(~scanf("%d",&n)) 10 { 11 int sum,t; 12 sum=0; 13 int P[101]; 14 for(int i=1;i<=n;i++) 15 { 16 scanf("%d",&P[i]); 17 sum+=P[i]; 18 } 19 t=sum/2; 20 int dp[45010]={0};//记的初始化 21 int sum1=0; 22 int sum2; 23 for(int i=1;i<=n;i++) 24 { 25 for(int j=t;j>=P[i];j--) 26 { 27 //状态转移方程 28 dp[j]=max(dp[j],dp[j-P[i]]+P[i]); 29 } 30 } 31 for(int i=1;i<=t;i++) 32 { 33 //寻找dp[]中的最大值 34 if(dp[i]>sum1) 35 sum1=dp[i]; 36 } 37 sum2=sum-sum1; 38 printf("%d %d\n",sum1,sum2); 39 } 40 41 return 0; 42 }
采药
题目描述
辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。
为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。
医师把他带到个到处都是草药的山洞里对他说:
“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。
我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入
输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出
可能有多组测试数据,对于每组数据,
输出只包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例输入
42 6 1 35 25 70 59 79 65 63 46 6 28 82 962 6 43 96 37 28 5 92 54 3 83 93 17 22 0 0
样例输出
117 334
这是一个标准的01背包问题,直接套标准模板吧
1 #include <cstdio> 2 #include <algorithm> 3 4 using namespace std; 5 6 const int maxm = 101; 7 const int maxt =1001; 8 9 int main() 10 { 11 int T,M; 12 while(~scanf("%d %d",&T,&M)&&T!=0&&M!=0) 13 { 14 int dp[maxt]={0},p[maxm],t[maxm]; 15 for(int i=1;i<=M;i++) 16 { 17 scanf("%d %d",&t[i],&p[i]); 18 } 19 for(int i=1;i<=M;i++) 20 { 21 for(int j=T;j>=t[i];j--) 22 { 23 dp[j]=max(dp[j],dp[j-t[i]]+p[i]); 24 } 25 } 26 int Max=0; 27 for(int i=0;i<=T;i++) 28 { 29 if(dp[i]>Max) 30 Max=dp[i]; 31 } 32 printf("%d\n",Max); 33 } 34 return 0; 35 }
搬寝室
题目描述
搬寝室是很累的,xhd深有体会.时间追述2006年7月9号,那天xhd迫于无奈要从27号楼搬到3号楼,因为10号要封楼了.看着寝室里的n件物品,xhd开始发呆,因为n是一个小于2000的整数,实在是太多了,于是xhd决定随便搬2*k件过去就行了.但还是会很累,因为2*k也不小是一个不大于n的整数.幸运的是xhd根据多年的搬东西的经验发现每搬一次的疲劳度是和左右手的物品的重量差的平方成正比(这里补充一句,xhd每次搬两件东西,左手一件右手一件).例如xhd左手拿重量为3的物品,右手拿重量为6的物品,则他搬完这次的疲劳度为(6-3)^2 = 9.现在可怜的xhd希望知道搬完这2*k件物品后的最佳状态是怎样的(也就是最低的疲劳度),请告诉他吧。
输入
每组输入数据有两行,第一行有两个数n,k(2<=2*k<=n<2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数).
输出
对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.
样例输入
5 1 18467 6334 26500 19169 15724 7 1 29358 26962 24464 5705 28145 23281 16827 0 0
样例输出
492804 1399489
动态方程:dp[i][j]=min(dp[i-1][j-2]+(a[j]-a[j-1])*(a[j]-a[j-1]),dp[i][j-1])
理解:dp[i][j]代表从j件物品里选取i对 首先把物品重量排序,当选取对数为i(i从1开始遍历)对时,j从2到n遍历(从2开始是因为选取一对时,物品件数最小为2)
物品数为j时(也就是j的当前值),涉及到两个问题:
1.选还是不选这个物品,若不选这个物品,那么就是从j-1件物品里选k对,即dp[i][j-1]
2.若选这个物品则上一个物品不能选,所以选取对数i要减去1,j要减去2,然后加上选的这个物品的平方差,即dp[i-1][j-2]+(a[j]-a[j-1])*(a[j]-a[j-1])
1 #include<stdio.h> 2 #include<algorithm> 3 #include<iostream> 4 #include<string> 5 #include<string.h> 6 #include<set> 7 #include<math.h> 8 using namespace std; 9 int dp[1005][2005]; 10 int a[2005]; 11 int main() 12 { 13 int n,k,i,j; 14 while(cin>>n>>k) 15 { 16 for(j=0;j<2005;j++) 17 dp[0][j]=0; 18 19 for(i=1;i<1005;i++) 20 for(j=0;j<2005;j++) 21 dp[i][j]=0x3fffffff; 22 23 for(i=1;i<=n;i++) 24 cin>>a[i]; 25 26 sort(a+1,a+n+1); 27 28 for(i=1;i<=k;i++) 29 for(j=2;j<=n;j++) 30 dp[i][j]=min(dp[i-1][j-2]+(a[j]-a[j-1])*(a[j]-a[j-1]),dp[i][j-1]); //动态转移方程 31 32 cout<<dp[k][n]<<endl; 33 } 34 }
解题思路:
题目意思为求n个物品,拿k对使得消耗的体力最少,或者说是这k对物品,每一对中两件物品的质量差平方最小,所以要使得质量差的平方小,只能排序后取质量相邻两个物品作为一对;
现在设f[i][j]为前i件物品组成k对所消耗的体力最小;
这时分两种情况含有第i件物品和不含有第i件物品(即第i件物品是不是含在第j对里)
1.含有i件物品 则有 f[i][j]=f[i-2][j-1]+(val[i]-val[i-1])*(val[i]-val[i-1]);
2.不含第i件物品则有 f[i][j]=f[i-1][j];
所以动态转移方程为:f[i][j]=minn(f[i-2][j-1]+(val[i]-val[i-1])*(val[i]-val[i-1]), f[i][j]=f[i-1][j]);
1 #include <stdio.h> 2 #include <stdlib.h> 3 #define size 2005 4 #define INIF 2147483646 5 6 7 int f[size][1005]; 8 int minn(int a,int b) 9 { 10 return a<=b?a:b; 11 } 12 int cmp(const void *a,const void *b) 13 { 14 return *(int *)a-*(int *)b;// 从小到大排序 15 } 16 int main() 17 { 18 int n,k,i,j; 19 int val[size]={0}; 20 f[0][0]=0; 21 while(scanf("%d%d",&n,&k)!=EOF) 22 { 23 24 val[0]=0; 25 for(i=1; i<=n; i++) 26 scanf("%d",&val[i]); 27 qsort(val+1,n,sizeof(val[0]),cmp); 28 for(i=0; i<=n; i++) 29 for(j=1; j<=k; j++) 30 f[i][j]=INIF; 31 32 for(i=2; i<=n; i++) 33 for(j=1; j*2<=i; j++) 34 f[i][j]=minn(f[i-2][j-1]+(val[i]-val[i-1])*(val[i]-val[i-1]),f[i-1][j]); 35 printf("%d\n",f[n][k]); 36 } 37 return 0; 38 }
题解:经典的dp 问题,设dp[i][j]表示的是已经拿到第i件物品时拿了2*k件
转移方程要么在前i件的时候包括第i件这样第i+1件一定要取此时dp[i][k] = dp[i-2][k-1]+a[i-1],要么就是不包括滴i件,那么在dp[i][k+1]一定等于dp[i-1][k],两种情况取最小
但是只要是dp一定要注意边界条件,先看转移方程中有哪些是自己一定要处理出来的初始数据,当k == 0 的时候所有dp都是0;当转移的时候如果是2*k>=i 则不再转移,因为不可能出现了,一定要想清楚什么时候可以继续转移
特别注意:对于任何一道题,都要提前想明白点的编号是从1开始还是从0开始,然后后面的所有程序都要按照这个规则去写,因为编号的问题出的错,程序长了是不好改的。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 #define INF 0x7f7f7f7f 6 #define N 2010 7 int v[N]; 8 int a[N]; 9 int dp[N][N]; 10 int main() 11 { 12 int n , k; 13 while(~scanf("%d%d",&n,&k)) 14 { 15 for(int i= 0 ;i < n ; i++) scanf("%d",&v[i]); 16 sort(v,v+n); 17 for(int i = 1 ; i < n ; i++) a[i-1] = (v[i]-v[i-1])*(v[i]-v[i-1]); 18 //memset(dp,0x7f,sizeof(dp)); 19 for(int i = 0 ; i < n ; i++) 20 for(int j = i/2+1 ; j< n ;j++) dp[i][j] = INF; 21 for(int j = 0 ; j < n ;j++) dp[j][0] = 0; 22 dp[1][1] = a[0]; 23 for(int i =2 ;i < n ; i++) 24 for(int j = 1 ; 2*j <= i+1 ;j++) 25 dp[i][j] = min(dp[i-1][j],dp[i-2][j-1]+a[i-1]); 26 printf("%d\n",dp[n-1][k]); 27 } 28 return 0; 29 }
ZZY 的爱好
题目描述
ZZY 爱足球~~爱音乐~~爱日剧~~爱电影~~爱 A 题~~爱萌妹~~总之 ZZY 喜欢做很多事情 ~~而且 ZZY 希望在这些爱好中能收获一些东西~~但是并不是所有爱好对所有目标都是起积 极作用的..ZZY 十分的困惑..于是 ZZY 列了下自己想获得的收获并且给每个目标设立了最小 要达到的权值...并且给自己的爱好对每个收获目标进行了评值..这个值若是负则代表不利于 获得某个收获~~为 0 代表没影响~~为正的代表利于获得某种收获..现在 ZZY 已经制作好了 这些数据想请你帮帮忙~~在保证所有的目标最低要求都能达成的情况下保留尽量多的爱好 ~~
输入
第一行为 ZZY 的收获目标个数 N ( 0<N<=20 )
第二行为 ZZY 对每个目标所订的一个最低权值 ( 0 < w <= 1000 ) 第三行为ZZY的爱好总数M ( 0 < M <= 16 )
下面的 M 行每行有 N 个数代表每个爱好对每个目标的促进权值..( -1000 <= k <= 1000 )
输出
么选 1 2) 若怎么都无法实现目标那只能说着所有爱好都要不得,输出 0...
样例输入
4 100 200 300 400 3 100 100 400 500 100 -10 50 300 100 100 -50 -50
样例输出
2 1 3
本题可以暴力枚举出来,不过要注意枚举的方法,可以用类似2进制的方法。对于每个爱好,若选择它则置为1,不选则为0。例如现在有3个目标,若选则第一个和第三个,则二进制形式为101,对应十进制形式为5。因此枚举1~2^M,就可以把每种情况都列举一遍。每次枚举时把相应的十进制数转化为二进制形式,判断每一位是1还是0,对应相应爱好是选择还是不选择。
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 #include<math.h> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 int v,g,a[31],i,j,s[31][31],p,k,num,ans[31],sum[31],m; 9 int t[31],tnum; 10 int main() 11 { 12 while (~scanf("%d",&v)) 13 { 14 for (i=1;i<=v;i++) 15 scanf("%d",&a[i]); 16 scanf("%d",&g); 17 for (i=1;i<=g;i++) 18 for (j=1;j<=v;j++) scanf("%d",&s[i][j]); 19 p=(int)pow(2,g); 20 num=0; 21 for (k=1;k<p;k++)//所有可能性 22 { 23 tnum=0; 24 i=k; j=0; 25 memset(sum,0,sizeof(sum)); 26 while (i)//遍历操作 27 { 28 j++; 29 if (i%2) 30 { 31 for (m=1;m<=v;m++) sum[m]+=s[j][m]; 32 t[++tnum]=j; 33 } 34 i/=2; 35 } 36 for (m=1;m<=v;m++)//判断是否满足要求 37 if (sum[m]<a[m]) goto A; 38 if (tnum>num)//爱好个数更多,更新列表 39 { 40 num=tnum; 41 for (m=1;m<=num;m++) ans[m]=t[m]; 42 }else 43 if (tnum==num)//爱好个数相等,取较小的数 44 { 45 for (m=1;m<=num;m++) 46 if (ans[m]<t[m]) goto A; 47 for (m=1;m<=num;m++) ans[m]=t[m]; 48 } 49 A: ; 50 } 51 printf("%d",num); 52 for (i=1;i<=num;i++) printf(" %d",ans[i]); 53 printf("\n"); 54 } 55 return 0; 56 }
开心的金明
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:
v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*为乘号)
请你帮助金明设计一个满足要求的购物单。
输入
输入文件happy.in 的第1行,为两个正整数,用一个空格隔开:
N m
(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有2个非负整数
v p
(其中v表示该物品的价格(v<=10000),p表示该物品的重要度(1~5))
输出
输出文件happy.out只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。
样例输入
1000 5 800 2 400 5 300 5 400 3 200 2
样例输出
3900
分析:01背包的裸题,不懂01背包的可以去看下背包九讲,这里的限定N元就是背包的体积,每个物品所花费的钱就可以认为是这个物品的体积,不过这里要求的是价格与重要度乘积的总和的最大值,所以将每个物品的重要度乘上物品的价格视为物品的价值。
1 #include<map> 2 #include<set> 3 #include<cmath> 4 #include<queue> 5 #include<stack> 6 #include<cstdio> 7 #include<vector> 8 #include<cctype> 9 #include<cstring> 10 #include<utility> 11 #include<cstdlib> 12 #include<iomanip> 13 #include<iostream> 14 #include<algorithm> 15 #define Clear(x) memset(x,0,sizeof(x)) 16 #define fup(i,a,b) for(int i=a;i<b;i++) 17 #define rfup(i,a,b) for(int i=a;i<=b;i++) 18 #define fdn(i,a,b) for(int i=a;i>b;i--) 19 #define rfdn(i,a,b) for(int i=a;i>=b;i--) 20 typedef long long ll; 21 typedef unsigned long long ull; 22 using namespace std; 23 const int maxn=3e4+7; 24 const int maxm=27; 25 int dp[maxn],w[maxm],v[maxm]; 26 27 28 int read() 29 { 30 char ch=getchar();int ret=0,f=1; 31 while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();} 32 while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();} 33 return f*ret; 34 } 35 36 int main() 37 { 38 int N=read(),m=read(); 39 rfup(i,1,m) 40 { 41 w[i]=read(),v[i]=read(); 42 v[i]*=w[i]; 43 } 44 rfup(i,1,m) 45 { 46 rfdn(j,N,w[i]) 47 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 48 } 49 printf("%d\n",dp[N]); 50 return 0; 51 }
本问题可以简单概括为:有m件物品和一个容量为n的背包。第i件物品的费用是v[i],价格与重要度乘积是p[i]=v[i]×w[i]。求解将哪些物品装入背包可使p[i]总和最大,即“01背包问题”。
1.状态的设计
f[i,j]记录“选取前i件物品花费j元钱”的最大价值,我们可以从第1 个物品出发,一直取到第i个物品,则可以分成i个阶段,我们以f[i,j]作为状态。
2.状态转移方程
f[i,j]=max{ f[i-1,j-v[i]]+v[i]×w[i],f[i-1,j]}(1≤i≤m,v[i]≤j≤n)
f[i,0]=0 (1≤i≤m),f[0,j]=0 (0≤j≤n)
3.优化空间
以上方法的空间复杂度为O(n×m),可以优化到O(n)。
首先,我们知道f[i,j]是由f[i-1,j]和f[i-1,j-v[i]]两个子问题决策而来;也就是说第i阶段只与第i-1阶段有关,可以使用滚动数组来优化空间。
继续分析,若将f[i,j]改为一维数组g[j],并将上面程序中for j:=v[i] to n do循环改为for j:=n downto v[i]do,这样的顺序计算g[j]时,可以保证g[j-v[i]]保存的是前一阶段状态f[i-1,j-v[i]]的值。
音量调节
Description
一个吉他手准备参加一场演出。他不喜欢在演出时始终使用同一个音量,所以他决定每一首歌之前他都要改变一次音量。在演出开始之前,他已经做好了一个列表,里面写着在每首歌开始之前他想要改变的音量是多少。每一次改变音量,他可以选择调高也可以调低。
音量用一个整数描述。输入文件中给定整数beginLevel,代表吉他刚开始的音量,以及整数maxLevel,代表吉他的最大音量。音量不能小于0也不能大于maxLevel。输入文件中还给定了n个整数c1,c2,c3…..cn,表示在第i首歌开始之前吉他手想要改变的音量是多少。
吉他手想以最大的音量演奏最后一首歌,你的任务是找到这个最大音量是多少。
Input
第一行依次为三个整数:n, beginLevel, maxlevel。
第二行依次为n个整数:c1,c2,c3…..cn。
Output
输出演奏最后一首歌的最大音量。如果吉他手无法避免音量低于0或者高于maxLevel,输出-1。
Sample Input
5 3 7
Sample Output
HINT
0<=beginlevel<=maxlevel
Source
一开始便开始想用f[i]表示前i个物品能够得到的最大的音量,以为和装箱问题一样。于是借用一下强大的搜索引擎,发现一种叫布尔型dp,就是用f[i][j]表示前i个物品在音量为j时可行;记住这种表示方式;
1 #include <iostream> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 int a[1010]; 8 bool dp[55][1010]; 9 10 int main() 11 { 12 int n,initial,maxl; 13 cin>>n>>initial>>maxl; 14 for(int i=1;i<=n;i++) 15 { 16 cin>>a[i]; 17 } 18 memset(dp,false,sizeof(dp)); 19 dp[0][initial]=true; 20 for(int i=1;i<=n;i++) 21 { 22 for(int j=0;j<=maxl;j++) 23 { 24 //dp[i][j]是第i次调节可以获得的音量j 25 dp[i][j]=((j+a[i]<=maxl)&&dp[i-1][j+a[i]])||((j-a[i]>=0)&&dp[i-1][j-a[i]]); 26 //j+a[i]<=maxl和 j-a[i]>=0判断是否超出dp[]的范围 27 //dp[i-1][j+a[i]]为真说明第i次调节后的j可以是第i-1次的某个音量减去a[i]得到 28 //dp[i-1][j-a[i]]为真说明第i次调节后的j可以是第i-1次的某个音量加上a[i]得到 29 } 30 } 31 for(int i=maxl;i>=0;i--) 32 { 33 if(dp[n][i]) 34 { 35 cout<<i<<endl; 36 return 0; 37 } 38 } 39 cout<<-1<<endl; 40 return 0; 41 }