1.01背包
hdu 2602 Bone Collector(01背包)
题意:给出包裹的大小v,然后给出n块骨头的价值value和体积volume,求出一路下来包裹可以携带骨头最大价值
思路:01背包
1.二维数组(不常用
/* 01背包 二维数组 */ #include<iostream> #include<stdio.h> #include<math.h> using namespace std; int dp[1100][1100]; int main() { int i,j; int t,n,_v;//测试用例个数,物品种类,背包大小 int c[1100],v[1100];//花费,价值 scanf("%d",&t); while(t--) { scanf("%d%d",&n,&_v); for(j=0; j<=_v; ++j)dp[0][j]=0; for(i=1; i<=n; ++i)scanf("%d",&v[i]); for(i=1; i<=n; ++i)scanf("%d",&c[i]); for(i=1; i<=n; ++i) { for(j=0; j<c[i]; ++j)dp[i][j]=dp[i-1][j]; for(j=c[i]; j<=_v; ++j) { if(dp[i-1][j-c[i]]+v[i]>dp[i-1][j])dp[i][j]=dp[i-1][j-c[i]]+v[i]; else dp[i][j]=dp[i-1][j]; } } printf("%d ",dp[n][_v]); } return 0; }
2.一维数组(之后 多重、完全背包 的解法 皆采用一维数组)
1...n
_v...0
/* 01背包 一维数组 */ #include<iostream> #include<stdio.h> using namespace std; int dp[1100]; int main() { int i,j; int t,n,_v;//测试用例个数,物品种类,背包大小 int c[1100],v[1100];//花费,价值 scanf("%d",&t); while(t--) { scanf("%d%d",&n,&_v); for(j=0; j<=_v; ++j)dp[j]=0; for(i=1; i<=n; ++i)scanf("%d",&v[i]); for(i=1; i<=n; ++i)scanf("%d",&c[i]); for(i=1; i<=n; ++i) for(j=_v; j>=c[i]; --j) if(dp[j-c[i]]+v[i]>dp[j])dp[j]=dp[j-c[i]]+v[i]; printf("%d ",dp[_v]); } return 0; }
2.多重背包
hdu 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(多重背包)
题意:给出经费的最大值n,再给出 种类m和每种的袋数c、价格p、重量h,求能买大米的最大重量
思路:每种物品有一个固定的次数上限。为多重背包问题。转换为01背包来做
以下方法,均为转化为01背包来做
思路1:物品不摊开,选取每一种时,进行讨论,(相当于竖着填背包v的一列,一列一列的填
码1:kj
/* 多重背包 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[110]; int main() { int i,j,k,tem; int t,n,_v;//测试用例个数,物品种类,背包大小 int c[110],v[110];//花费,价值 int num[110];//每种物品个数 int tc,tv;//拆分时物品花费,价值 scanf("%d",&t); while(t--) { scanf("%d%d",&_v,&n); memset(dp,0,sizeof(dp)); for(i=1; i<=n; ++i)scanf("%d%d%d",&c[i],&v[i],&num[i]); ////// for(i=1; i<=n; ++i) for(k=_v; k>=c[i]; --k) for(j=1; j<=num[i]&&j*c[i]<=k; ++j)//此处比01背包多了一层循环 { tc=j*c[i]; tv=j*v[i]; tem=dp[k-tc]+tv; if(tem>dp[k])dp[k]=tem; } // printf("%d ",dp[_v]); } return 0; }
思路2:物品摊开,然后处理01背包(相当于横着填物品的一行,一行一行的填
2.1朴素拆分,把每种物品展开,既有n件a物品,则进行n件是否选取操作
码2:jk
/* 多重背包 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[110]; int main() { int i,j,k,tem; int t,n,_v;//测试用例个数,物品种类,背包大小 int c[110],v[110];//花费,价值 int num[110];//每种物品个数 int tc,tv;//拆分时物品花费,价值 scanf("%d",&t); while(t--) { scanf("%d%d",&_v,&n); memset(dp,0,sizeof(dp)); for(i=1; i<=n; ++i)scanf("%d%d%d",&c[i],&v[i],&num[i]); ////// for(i=1; i<=n; ++i) for(j=1; j<=num[i]&&j*c[i]<=_v; ++j)//此处比01背包多了一层循环 for(k=_v; k>=j*c[i]; --k) { tem=dp[k-c[i]]+v[i]; if(tem>dp[k])dp[k]=tem; } // printf("%d ",dp[_v]); } return 0; }
hint:2.1代码与思路1 代码 只是循环次序不一样而已,实际是一样的操作。(并且时间复杂度相同,略麻烦。
因为
思路1:
for(k=_v; k>=c[i]; --k)
for(j=1; j<=num[i]&&j*c[i]<=k; ++j)
思路2:
for(j=1; j<=num[i]&&j*c[i]<=_v; ++j)
for(k=_v; k>=j*c[i]; --k)
大体看一下这俩循环,
这两个循环做的循环次数是相同的,也就是说,计算次数相同,时间复杂度相同
思路2.2 二进制拆分,有n件a物品,则拆成 1,2,4,8,...,q 这样,即 1+2+4+...+q=n
码3:
/* 多重背包 二进制拆分 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[110]; int main() { int i,j,k,tem; int t,n,_v;//测试用例个数,物品种类,背包大小 int c[110],v[110];//花费,价值 int num[110];//每种物品个数 int tc,tv;//拆分时物品花费,价值 scanf("%d",&t); while(t--) { scanf("%d%d",&_v,&n); memset(dp,0,sizeof(dp)); for(i=1; i<=n; ++i)scanf("%d%d%d",&c[i],&v[i],&num[i]); ////// for(i=1; i<=n; ++i) { for(j=1; j<=num[i]; num[i]=num[i]-j,j=j*2)//此处比01背包多了一层循环 { tc=j*c[i];//拆分后物品花费 tv=j*v[i];// for(k=_v; k>=tc; --k) { tem=dp[k-tc]+tv; if(tem>dp[k])dp[k]=tem; } } if(num[i]>0) //如果还有物品,num[i] 即相当于 1+2+4+...+q 中的 q { tc=num[i]*c[i]; tv=num[i]*v[i]; for(k=_v; k>=tc; --k) { tem=dp[k-tc]+tv; if(tem>dp[k])dp[k]=tem; } } } // printf("%d ",dp[_v]); } return 0; }
3.完全背包
hdu 1114 Piggy-Bank(完全背包)
题意:给出一个存钱罐的容量,给出n种硬币的价值p和重量w(注意:每种硬币可无限取)
1.如果存钱罐能够正好塞满,输出塞满存钱罐需要的最少硬币的价值。
2.若不能完全塞满,则输出impossible。
思路:每种物品可以放无限多次。所以为完全背包问题。此题是求最小值,为完全背包的变形。
注意初始化 dp[ 0 ]=0;
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
/* 完全背包 */ #include<iostream> #include<stdio.h> using namespace std; int main(){ int t,e,f;//测试用例,空存钱罐的重量,装满后存钱罐的重量 int n,p,w;//硬币种类,价值,重量 int dp[11000];//最小价值 dp[0]=0;//初始化为0 scanf("%d",&t); while(t--){ scanf("%d%d",&e,&f); int v=f-e;//存钱罐的容量 int i,j; for(i=1;i<=v;i++) dp[i]=1234567890; scanf("%d",&n); for(i=0;i<n;i++){ scanf("%d%d",&p,&w); for(j=w;j<=v;j++){ int tem=dp[j-w]+p; if(tem<dp[j]) dp[j]=tem; } } if(dp[v]==1234567890) printf("This is impossible. "); else printf("The minimum amount of money in the piggy-bank is %d. ",dp[v]); } return 0; }
4.最长上升子序列O(nlogn)
/* 最长上升子序列O(nlogn) */ #include<iostream> #include<stdio.h> using namespace std; const int MAXN=500010; int a[MAXN],b[MAXN]; //b[k]是序列a中所有长度为k的递增子序列中的最小结尾元素值 //用二分查找的方法找到一个位置,使得num>b[i-1]并且num<b[i],并用num代替b[i] int Search(int num,int low,int high){ int mid; while(low<=high){ mid=(low+high)/2; if(num>=b[mid])low=mid+1; else high=mid-1; } return low; } int DP(int n){ int i,len,pos; b[1]=a[1]; len=1; for(i=2;i<=n;i++){ if(a[i]>=b[len]){//如果a[i]比b[]数组中最大还大直接插入到后面即可 len=len+1; b[len]=a[i]; } else{//用二分的方法在b[]数组中找出第一个比a[i]大的位置并且让a[i]替代这个位置 pos=Search(a[i],1,len); b[pos]=a[i]; } } return len; } int main(){ a[1]=1; a[2]=2; a[3]=3; a[4]=0; printf("%d ",DP(4)); return 0; }