洛谷普及场背包问题

1. P1060 开心的金明

解题思路

最基本的01背包问题,如果不用优化成一维数组,那么用f(i,j)表示前i个商品花j元钱获得的最大价值。按第i个商品放与不放可以分为:
f[i,j]=max(f[i-1,j],f[i-1,j-v[i]]+v[i]*w[i])

#include <iostream>
#include<cstdio>
using namespace std;
int f[30][30005];//总的商品个数和总的钱数
int v[30],w[30];
//f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+v[i]*w[i]); 
int main(int argc, char** argv) {
    int N,m;
    scanf("%d %d",&N,&m);
    for(int i=1;i<=m;i++) scanf("%d %d",&v[i],&w[i]);
    for(int i=1;i<=m;i++){
        for(int j=0;j<=N;j++) f[i][j]=f[i-1][j];//先更新,再判断 
        for(int j=v[i];j<=N;j++){
            f[i][j]=max(f[i][j],f[i-1][j-v[i]]+v[i]*w[i]);
        }
    }
    
    printf("%d\n",f[m][N]);
    return 0;
}

优化成一维数组可以表示成:其中 j j j表示总的钱, i i i表示第 i i i个商品, f [ j ] f[j] f[j]表示用金额为 j j j的钱获得目标的最高价值。
f [ j ] = m a x ( f [ j ] , f [ j − a [ i ] . v ] + a [ i ] . v ∗ a [ i ] . p ) f[j]=max(f[j],f[j-a[i].v]+a[i].v*a[i].p) f[j]=max(f[j],f[ja[i].v]+a[i].va[i].p)

#include<iostream>
#include<cstdio> 
using namespace std;
//f[j]=max(
struct node{
    int v,p;
}a[30];
int f[30100];
int main(int argc, char** argv) {
    int N,m;
    scanf("%d%d",&N,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a[i].v,&a[i].p);
    }

    for(int i=1;i<=m;i++){
        for(int j=N;j>=a[i].v;j--){
            f[j]=max(f[j],f[j-a[i].v]+a[i].v*a[i].p);
        }
    }
    
    printf("%d\n",f[N]);
    return 0;
}

2. P1164 小A点菜

解题思路

参考自:https://www.luogu.org/blog/user31798/solution-p1164
与1有点变化,这里的背包要求更加严格,即必须装满整个背包,不能有空余的空间。这里利用f[i][j]表示前i个商品刚好花j元钱所得到的总的方案数。

5 4  //总的商品个数和总的钱数
1 1 2 2 4//每个商品需要花的钱
  1. 如果每个商品需要花的钱分别为1,1,2,2,4,那么显然有f[5][4]=f[4][4]+1,因为最后一个商品和总的钱数相等,买前5个商品的总的方案数等于买前四个商品的方案数加一。即如果当j==a[i]时,有:f[i][j]=f[i][j-1]+1;
  2. 如果每个商品需要花的钱分别为1,1,2,2,5,那么有f[5][4]=f[4][4],因为最后一个商品的价格比总价还高,不可能与前面的商品组合成新的方案数。即如果j>a[i]时,f[i][j]=f[i-1][j];
  3. 如果每个商品的花费分别为1,1,2,2,3,最后一个商品可以和前面的商品组合成更多的商品数,有f[5][4]=f[4][4]+f[4][4-3]=f[4][4]+f[4][1],即如果a[i]<j,那么a[i]可以和前面的商品组成更多的组合数,f[i][j]=f[i-1][j]+f[i-1][j-a[i]]。

所以递推关系可以表示成:
当j==a[i]时:f[i][j]=f[i-1][j]+1
当j>a[i]时,f[i][j]=f[i-1][j]+f[i-1][j-a[i]]
当j<a[i]时,f[i][j]=f[i-1][j]

#include<iostream>
#include<cstdio>
using namespace std;
int a[110],f[110][10010]={0};
int main(int argc, char** argv) {
	int n,m,ans=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(j==a[i]) f[i][j]=f[i-1][j]+1;//==
			if(j>a[i]) f[i][j]=f[i-1][j]+f[i-1][j-a[i]];
			if(j<a[i]) f[i][j]=f[i-1][j]; 
		}
	} 
	printf("%d\n",f[n][m]);
	return 0;
}

3. P1064 金明的预算方案

解题思路

参考自https://www.luogu.org/blog/user20197/solution-p1064
分层dp有依赖性的背包问题,注意到这里的附件必须与主件结合才能使用。首先直接对所有主件按一般背包问题进行处理;然后对于特定的主件引入附件再采用背包的思想进行处理,若引入附件的结果优于未引入主件的背包问题,则更新最优的结果。

#include<iostream>
#include<cstdio>
using namespace std;
struct node{
	int v,p,q;
}a[65],b[65][1000]; //a记录所有信息,b记录附件信息

int N,m;
int f[32100];
int index[1000]; 
int main(int argc, char** argv) {
	scanf("%d%d",&N,&m);
	
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&a[i].v,&a[i].p,&a[i].q);
		if(a[i].q!=0){//附件 
			index[a[i].q]++;
			b[a[i].q][index[a[i].q]].v=a[i].v;
			b[a[i].q][index[a[i].q]].p=a[i].p;
		}
	}

	for(int i=1;i<=m;i++){
		for(int j=N;a[i].q==0&&j>=a[i].v;j--){
			f[j]=max(f[j],f[j-a[i].v]+a[i].v*a[i].p);//主件 
			
			if(j>=a[i].v+b[i][1].v)//附件1
				f[j]=max(f[j],f[j-a[i].v-b[i][1].v]+a[i].v*a[i].p+b[i][1].v*b[i][1].p);
				
			if(j>=a[i].v+b[i][2].v)//附件2
				f[j]=max(f[j],f[j-a[i].v-b[i][2].v]+a[i].v*a[i].p+b[i][2].v*b[i][2].p);
				
			if(j>=a[i].v+b[i][1].v+b[i][2].v)//附件1,2
				f[j]=max(f[j],f[j-a[i].v-b[i][1].v-b[i][2].v]+a[i].v*a[i].p+b[i][1].v*b[i][1].p+b[i][2].v*b[i][2].p);
		}
	}
	
	printf("%d\n",f[N]);
	return 0;
}

4. P1616 疯狂的采药

解题思路

完全背包问题,因为每个商品的数量理论上来说是无限的。这时候如果直接转成01背包问题也可以完成。如总的时间为 T T T,这时候每个商品的数量可化为有限的 T a [ i ] . t \frac{T}{a[i].t} a[i].tT,然后再对所有商品利用01背包的思想来解决,时间复杂度为 O ( M ∑ i = 1 M T a [ i ] . t ) O( M\sum_{i=1}^{M} \frac{T}{a[i].t}) O(Mi=1Ma[i].tT)
如果换一种思路不将每个商品的具体数量求出来,则需要对 f [ j ] f[j] f[j]更新时的顺序进行调整。之前在更新 f [ j ] f[j] f[j]时,对于每个 j j j从总的时间 T T T往回更新,这是为了保证在更新二维情况下的 f [ i ] [ j ] f[i][j] f[i][j]时,之前的 f [ i ] [ j − a [ i ] . v ] f[i][j-a[i].v] f[i][ja[i].v]未做改变,即保证第 i i i个商品只有两种状态,要么被选,要么不被选。但是现在的第 i i i个商品的数量是无限的,所以在更新 f [ i ] [ j ] f[i][j] f[i][j]时,应该保证之前的 f [ i ] [ j − a [ i ] . v ] f[i][j-a[i].v] f[i][ja[i].v]做了改变,即第 i i i个商品可以多次被选。
所以将原来从总的时间 T T T往回更新,换成 a [ i ] . v a[i].v a[i].v T T T顺序更新即可。时间复杂度为 O ( M T ) O(MT) O(MT)

#include <iostream>
#include<cstdio>
using namespace std;
struct node{
    int t,v;
}a[10100];
int f[100010];//M,T 
int main(int argc, char** argv) {
    int T,M;
    scanf("%d%d",&T,&M);
    for(int i=1;i<=M;i++) scanf("%d%d",&a[i].t,&a[i].v);
    for(int i=1;i<=M;i++){
        for(int j=a[i].t;j<=T;j++){//顺序更新
            f[j]=max(f[j],f[j-a[i].t]+a[i].v);
        }
    } 
    printf("%d\n",f[T]);
    return 0;
}

5. P1156 垃圾陷阱

解题思路:

非常有意思的一道题,是01背包的变种(加强版),对于题目中的每个垃圾分为吃或不吃两种状态,设 f [ i ] [ j ] f[i][j] f[i][j]表示到第 i i i个垃圾达到高度为 j j j时剩下的时间。
(1)吃: f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j ] + a [ i ] . F − ( a [ i ] . T − a [ i − 1 ] . T ) ) f[i][j]=max(f[i][j],f[i-1][j]+a[i].F-(a[i].T-a[i-1].T)) f[i][j]=max(f[i][j],f[i1][j]+a[i].F(a[i].Ta[i1].T))
(2)不吃: f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − a [ i ] . H ] − ( a [ i ] . T − a [ i − 1 ] . T ) ) f[i][j]=max(f[i][j],f[i][j-a[i].H]-(a[i].T-a[i-1].T)) f[i][j]=max(f[i][j],f[i][ja[i].H](a[i].Ta[i1].T))
如果最后 j j j转化不到总高度 D D D,那么存活的最长时间= m a x ( f [ i ] [ j ] + a [ i ] . T ) max(f[i][j]+a[i].T) max(f[i][j]+a[i].T),即当前时间 a [ i ] . T a[i].T a[i].T加上当前剩余的时间 f [ i ] [ j ] f[i][j] f[i][j]
最后注意按垃圾出现的时间升序排列。

#include <iostream>
#include<cstdio>
#include<algorithm> 
using namespace std;
#define minv -233233233
struct node{
    int t,f,h;
    bool operator < (const node &b) {
        return t<b.t;//时间升序; 
    }
}a[110];
int d,g;
int f[110][110];//i商品,j高度,f[i][j]剩下的生命时间 
int main(int argc, char** argv) {
    scanf("%d%d",&d,&g);
    for(int i=1;i<=g;i++){
        scanf("%d%d%d",&a[i].t,&a[i].f,&a[i].h);
    }
    sort(a+1,a+g+1);
    for(int i=1;i<=110;i++) for(int j=0;j<=110;j++) f[i][j]=minv;
    
    f[0][0]=10; int flag=0;
    a[0].f=0;a[0].h=0;a[0].t=0; 
    for(int i=1;i<=g;i++){
        for(int j=0;j<=d;j++){
            if(f[i-1][j]-(a[i].t-a[i-1].t)>=0)
                f[i][j]=max(f[i][j],f[i-1][j]+a[i].f-(a[i].t-a[i-1].t));//吃 
            if(j-a[i].h>=0&&f[i-1][j-a[i].h]-(a[i].t-a[i-1].t)>=0){
                f[i][j]=max(f[i][j],f[i-1][j-a[i].h]-(a[i].t-a[i-1].t));//不吃 
                if(j==d){//如果能逃出来,一定是由其他状态转化而来 
                    flag=1;
                    printf("%d\n",a[i].t);
                    return 0;
                }
            }
        }
    }
    if(!flag){
        int ans=-1;
        for(int i=1;i<=g;i++){
            for(int j=0;j<=d;j++){
                if(f[i][j]!=minv){
                    ans=max(ans,f[i][j]+a[i].t);
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2020-09-05 19:33  xzhws  阅读(96)  评论(0编辑  收藏  举报