/*标准的01背包问题。状态转移方程f[i][v] = max{f[i-1][v-c[i]]+v[i],f[i-1][v]}*/#include<iostream>#include<string.h>#include<stdio.h>using namespace std;int main(){int T,N,V,f[1001],vol[1001],val[1001],tem;cin>>T;while(T--){cin>>N>>V;for(int i=0;i<N;i++){cin>>val[i];}for(int i=0;i<N;i++){cin>>vol[i];}memset(f,0,sizeof(f));for(int i=0;i<N;i++){for(int j=V;j>=vol[i];j--){tem=f[j-vol[i]]+val[i];if(f[j]<tem)f[j]=tem;}}cout<<f[V]<<endl;}system("pause");return 0;}
Coins
#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;int dp[100010],w[110],c[110];int main(){ int n,m,i,j,k,cnt; while( scanf("%d%d",&n,&m),n+m ) { cnt=0; memset(dp,0,sizeof(dp)); for(i=1;i<=n;i++) scanf("%d",&w[i]); for(i=1;i<=n;i++) scanf("%d",&c[i]); for(i=1;i<=n;i++) { if( c[i]*w[i] > m ) // 完全背包 { for( j=w[i];j<=m;j++ ) dp[j]= max( dp[j],dp[j-w[i]]+w[i] ); } //多重背包 转化为 01背包 ,//把物品的个数 c[i] 拆分为2^0,2^1,……,2^k,c[i]-2^k+1; //1至c[i]中的数肯定能够分解为前面的数的和。就是十进制转二进制else { k=1; int num=c[i]; while( k < num ) { for( j=m; j>=k*w[i] ; j--) dp[j]=max( dp[j],dp[j-k*w[i]]+k*w[i]); //拆分是对c[i]来说的,//ij具体操作时其实是把2^k个物品组合为一个物品来操作 num-=k; k<<=1; } for( j=m;j>=num*w[i];j--) dp[j]=max( dp[j],dp[j-num*w[i]]+num*w[i] ); } } for(i=1;i<=m;i++) if( dp[i]==i ) cnt++; printf("%d\n",cnt); } return 0;}
Ahui Writes Word
/*题目大意: Ahui写作文,每个单词都有复杂度和value,先输入N,C,代表整篇作文有n个单词, 然后作文的最大复杂度小于V。(1 ≤ N ≤ 100000, 1 ≤ C ≤ 10000),后面跟N行, 每行输入单词,还有单词的价值val跟单词的复杂度compl(0<=val<=com<=10),求在单词的复杂度不超过C的情况下,Ahui能够获得的最大价值。解题思路:一般的01背包会TLE,因为N * C = 1000000000;但是仔细考虑下,其实每个单词v跟c都小于等于10,所以这么大的输入量,必定有单词的价值跟复杂度是完全相同的,因为10*10=100;因此可以考虑用多重背包减小时间复杂度。P03: 多重背包问题题目:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。基本算法:这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}复杂度是O(V*Σn[i])。转化为01背包问题另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为<math>O(V*Σlog n[i])的01背包问题,是很大的改进。下面给出O(log amount)时间处理一件多重背包中物品的过程,其中amount表示物品的数量:procedure MultiplePack(cost,weight,amount) if cost*amount>=V CompletePack(cost,weight) return integer k=1 while k<amount ZeroOnePack(k*cost,k*weight) amount=amount-k k=k*2 ZeroOnePack(amount*cost,amount*weight)希望你仔细体会这个伪代码,如果不太理解的话,不妨翻译成程序代码以后,单步执行几次,或者头脑加纸笔模拟一下,也许就会慢慢理解了。O(VN)的算法多重背包问题同样有O(VN)的算法。这个算法基于基本算法的状态转移方程,但应用单调队列的方法使每个状态的值可以以均摊O(1)的时间求解。由于用单调队列优化的DP已超出了NOIP的范围,故本文不再展开讲解。我最初了解到这个方法是在楼天成的“男人八题”幻灯片上。小结这里我们看到了将一个算法的复杂度由O(V*Σn[i])改进到O(V*Σlog n[i])的过程,还知道了存在应用超出NOIP范围的知识的O(VN)算法。希望你特别注意“拆分物品”的思想和方法,自己证明一下它的正确性,并将完整的程序代码写出来。*/#include<stdio.h> #include<string.h> char ch[100] ; int nkind , total_comp ; int value[100024] , comp[100024] , fine[100024] , map[11][11]; int main () { int pos , x , y ,sum , t ; while ( scanf ( "%d%d" , &nkind , &total_comp ) != EOF ) { memset( map , 0 , sizeof (map) ) ; for ( int i = 0 ; i < nkind ; i ++ ) { scanf ( "%s%d%d" , ch , &x , &y ) ; map[x][y] ++ ; } pos = 0 ; // 用二分制转换成01背包 for ( int i = 0 ; i <= 10 ; i ++ ) for ( int j = 0 ; j <= 10 ; j ++ ) { if( map[i][j] >= 1 ) { sum = 1 , t = 1 ; while ( sum <= map[i][j] ) { value[pos] = i * t ; comp[pos++] = j * t ; t *= 2 ; sum += t ; } if ( ( sum - t ) < map[i][j] ) { value[pos] = i * (map[i][j]-sum + t) ; comp[pos++] = j * (map[i][j]-sum + t) ; } } } memset( fine , 0 , sizeof (fine) ) ; for ( int i = 0 ; i < pos ; i ++ ) for ( int j = total_comp ; j >= comp[i] ; -- j ) if ( fine[j] < fine[j-comp[i]] + value[i] ) fine[j] = fine[j-comp[i]] + value[i] ; printf ( "%d\n" , fine[total_comp] ) ; } return 0 ; }
Robberies
/*状态转移方程是:dp[j]=max(dp[j],dp[j-m[i]]*(1 - q[i])) 其中,dp[j]表示抢j块大洋的最大的逃脱概率,条件是dp[j-m[i]]可达,也就是之前抢劫过;始化为:dp[0]=1,其余初始化为-1.*/ #include<iostream>using namespace std;double max(double a,double b){ return a>b?a:b;}int main(){ int t; cin>>t; for(int x =1; x <= t; x++) { double p; cin>>p; int n; cin>>n; int i,j; int* m = new int[n+1]; double * q = new double[n+1]; int s = 0; for(i = 1; i <= n; i++) { cin>>m[i]>>q[i]; s += m[i]; } double *dp = new double[s+1]; dp[0] = 1; for(i = 1; i <= n; i++) for(j = s; j >= m[i]; j--) { dp[j] = max(dp[j],dp[j - m[i]]*(1-q[i])); } for(i = s; i >= 0; i--) if(dp[i] >= 1-p) { cout<<i<<endl; break; } }}
Watch The Movie
#include<cstdio>#include<cstring>#define max(a,b) a>b?a:b#define inf 0x7fffffff int N,M,L,t[105],v[105],dp[105][1005];int main(){ int T,i,j,k; scanf("%d",&T); while(T--) { //想买N部(即物品数量),最多卖M部(背包容量),最大时限为L //每部耗时t[i],价值v[i] //求在不超过最大时限L,且看完所有M部电影 的情况下获得的最大价值 //第一层背包:时间背包; 第二层背包:数量背包 scanf("%d%d%d",&N,&M,&L); for(i=0;i<N;i++) scanf("%d%d",&t[i],&v[i]); //初始化dp for(i=0;i<=M;i++) for(j=0;j<=L;j++) { if(i==0) dp[i][j]=0; else dp[i][j]=-inf; } //二维背包dp for(j=0;j<N;j++) //注意枚举N个物品的变量必须放在最外层 for(k=L;k>=t[j];k--) for(i=1;i<=M;i++) dp[i][k]=max(dp[i][k],dp[i-1][k-t[j]]+v[j]); //判断 if(dp[M][L]>0)printf("%d\n",dp[M][L]); else printf("0\n"); }}
饭卡
/*这是一个变形的01背包问题,首先如果金额小于5元,剩余金额不变,为已有金额。如果大于等于5元我们先用5元买最贵的菜。然后用剩下的钱买其他的菜这时就是一个典型的01背包问题了;求出最大的花费,然后用总金额减去最大的花费即为剩余金额。抽象的地方是花费和价值都是用金额表示。*/#include <iostream> #include <stdio.h> #include <algorithm> #include <string.h> using namespace std; bool cmp(int a,int b) { return a<b; } int main() { int n,k[1001],f[1001],m; while(scanf("%d",&n),n) { for(int i = 0; i < n; i++) { scanf("%d",&k[i]); } sort(k,k+n,cmp); scanf("%d",&m); if(m>=5) { memset(f,0,sizeof(f)); for(int i = 0; i < n-1; i++) for(int j = m-5; j>=k[i];j--) { if(f[j-k[i]]+k[i]>f[j]&&f[j-k[i]]+k[i]<=m-5)//注意这里的f[j-k[i]]+k[i]<=m-5。 f[j] = f[j-k[i]]+k[i]; } printf("%d\n",m-f[m-5]-k[n-1]); } else printf("%d\n",m); } return 0; }
Watch The Movie
#include<cstdio>#include<cstring>#define max(a,b) a>b?a:b#define inf 0x7fffffff int N,M,L,t[105],v[105],dp[105][1005];int main(){ int T,i,j,k; scanf("%d",&T); while(T--) { //想买N部(即物品数量),最多卖M部(背包容量),最大时限为L //每部耗时t[i],价值v[i] //求在不超过最大时限L,且看完所有M部电影 的情况下获得的最大价值 //第一层背包:时间背包; 第二层背包:数量背包 scanf("%d%d%d",&N,&M,&L); for(i=0;i<N;i++) scanf("%d%d",&t[i],&v[i]); //初始化dp for(i=0;i<=M;i++) for(j=0;j<=L;j++) { if(i==0) dp[i][j]=0; else dp[i][j]=-inf; } //二维背包dp for(j=0;j<N;j++) //注意枚举N个物品的变量必须放在最外层 for(k=L;k>=t[j];k--) for(i=1;i<=M;i++) dp[i][k]=max(dp[i][k],dp[i-1][k-t[j]]+v[j]); //判断 if(dp[M][L]>0)printf("%d\n",dp[M][L]); else printf("0\n"); }}
Proud Merchants
/*按照q-p从小到大排序,然后01背包。至于按照q-p从小到大排序比较难想到。q-p其实就是不更新的范围,不更新的范围从小到大递增时就不会影响后面的DP了。*/#include<stdio.h>#include<string.h>#include<algorithm>#include<iostream>using namespace std;const int MAXN=550;struct Node{ int p,q,v;}node[MAXN];int dp[5500];bool cmp(Node a,Node b)//按照 q-p 从小到大排序{ return a.q-a.p < b.q-b.p;}int main(){ int n,m; while(scanf("%d%d",&n,&m)!=EOF) { for(int i=0;i<n;i++) scanf("%d%d%d",&node[i].p,&node[i].q,&node[i].v); sort(node,node+n,cmp); memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++) for(int j=m;j>=node[i].q;j--) dp[j]=max(dp[j],dp[j-node[i].p]+node[i].v); printf("%d\n",dp[m]); } return 0;}