考研计试第十一天

问题6: 有一堆柑橘,重量为0到2000,总重量不大于2000。要求从中取出两堆放在扁担的两头且两头的重量相等,问符合条件的每堆重量最大为多少,没有符合条件的分堆方式则输出-1。

设状态dp[i][j]表示前i个柑橘被选择后(每个柑橘可能放到第一堆或者第二堆)后,第一堆比第二堆重 j 时,两堆的最大总重量和。dp[0][0]表示不往两堆中加任何柑橘,dp[0][j]表示负无穷,即其他状态都不存在。设当前加入柑橘重量为list[i]
dp[i][j] = max(dp[i-1][j - list[i] ] + list[i],dp[i-1][j + list[i] + list[i]],dp[i-1][j])
用该状态方程求出所有的状态后,状态dp[n][0]/2即为所求
柑橘总重不大于2000,所以总的状态数量为柑橘总数n22000,状态转移为O(1),故综合时间复杂度为O(4000*n),在可接收范围内。

#include<stdio.h>
#define OFFSET 2000//因为差值存在负值情况,故加上偏移值
int dp[101][4001];//保存状态
int list[101];
#define INF 0x7fffffff//无穷
int main(){
    int T;
    int case = 0;
    scanf("%d",&T);
    while(T--!=0){ //T次循环
         int n;
         scanf("%d",&n);
         bool HaveZero = false;//统计是否存在重量为0的柑橘
         int cnt = 0;//记录共有多少个重量非0的柑橘
         for(int i = 1;i <= n;i++){
            scanf("%d",&list[++cnt]);
            if(list[cnt] == 0){
               cnt--;//除去重量为0的柑橘
               HaveZero = true;//并记录之
            }
         }
         n = cnt;
         for(int i = -2000;i <= 2000;i++){//初始化
            dp[0][i+OFFSET] = -INF;
         }
         dp[0][0+OFFSET] = -INF;//dp[0][0] = 0
         for(int i = 1;i <= n;i++){//遍历每个柑橘
            for(int j = -2000;j <= 2000;j++){//便利每种可能出现的重量差
               int tmp1 = -INF,tmp2 = -INF;
       //分别记录当前柑橘放在第一堆或第二堆时转移得来的新值,若无法转移则为-INF
               if(j+list[i]<=2000&&dp[i-1][j+list[i]+OFFSET]!=-INF){
       //当状态可以由放在第一堆转移而来时
                  tmp1 = dp[i-1][j+list[i]+OFFSET]+list[i];//记录转移值
               if(j-list[i]>=-2000&&dp[i-1][j-list[i]+OFFSET]!=-INF)
        //当状态可以由放在第一堆转移而来时
                  tmp2 = dp[i-1][j-list[i]+OFFSET]+list[i];
               if(tmp1<tmp2) tmp1 = tmp2;//取两者中最大值,保存至tmp1
               if(tmp1<dp[i-1][j+OFFSET])
  //将tmp1与当前柑橘不放入任何堆即状态差不发生改变的原状态值比较,取较大的值保存至tmp1
                  tmp1 = dp[i-1][j+OFFSET];
               dp[i][j+OFFSET] = tmp1;
              //当前值状态保存为三个转移来源转移得到的新值中最大的那个
            }
        }
        printf("Case %d: ",++cas);
        if(dp[n][0+OFFSET] == 0){
           puts(HaveZero == true ? "0":"-1");
        else printf("%d\n",dp[n][0+OFFSET]/2);
     }
     return 0;
}

注意:顶级难度

(4)背包问题
问题7:一个山洞里有一些不同的草药,采每一株都需要些时间,每一株也有它自身的价值,在一段时间T内,采一些草药,使得采到的草药总价值最大。

分析:问题抽象为:有一个容量为V的背包,和一些物品。这些物品分别有两个属性,体积W和价值V,每种物品只有一个。要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。每个物品都有两种可能的情况,即在背包中存在或者不存在(背包中有0个或者1个该物品),归纳为0-1背包问题。
解答:用动态规划,由dp[i][j]表示在总体积不超过j的情况下,前i个物品所能达到的最大价值。dp[i][j] = dp[i-1][j-w] + v表示在总体积不超过j-w的情况下前i-1件物品可组成的最大价值。若物品不放入包中,则dp[i][j] = dp[i-1][j].
状态转移方程为:dp[i][j] = max{dp[i-1][j-w]+v,dp[i-1][j]};
也可以简化为:dp[j] = max{dp[j-list[i].w]+v,dp[j]]};

#include<stdio.h>
#define INF 0x7fffffff
int max(int a,int b){return a>b ? a:b;}
struct E{//保存物品信息结构体
     int w;
     int v;
}list[101];
int dp[101][1001];//记录状态数组
int main(){
    int s,n;
    while(scanf("%d%d",&s,&n) != EOF){
         for(int i = 1;i <= n;i++){
             scanf("%d%d",&list[i].w,&list[i].v);
         }
         for(int i = 0;i <= s;i++){//初始化
             dp[0][i] = 0;
         }
         for(int i = 0;i <= s;i++){
            for(int j = s;j >= list[i].w;j--){
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-list[i].w]+list[i].v);
            ]
            for(int j = list[i].w-1;j>=0;j--){
                dp[i][j] = dp[i-1][j];
            }
            printf("%d\n",dp[n][s]);
         }
         return 0;
  }

时间复杂度为为O(n*s),空间复杂度为O(s)

问题8:扩展0-1背包问题,使每种物品的数量无限增加,便得到完全背包问题:有一个容积为V的背包,同时有n个物品,每个物品均有各自的体积w和价值v,每个物品数量均有无限个,求使用该背包最多能装的物品价值总和。
问:有一个储蓄罐,告知其空时的重量和当前重量,并给定一些纸币的价值和响应的重量,求储蓄罐中最少有多少现金

分析:该问题要求钱币和空储蓄罐的重量恰好达到总重量,即在背包问题中表现为背包恰好装满,为完全背包问题

#include<stdio.h>
#define INF 0x7fffffff
int min(int a,int b){return a<b ? a:b}
struct E{
     int w;//重量
     int v;//价值
}list[501];
int dp[10001];//状态
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int s,tmp;
        scanf("%d%d",&tmp,&s);//输入空储蓄罐数量,和装满钱币的储蓄罐重量
        s -= tmp;//计算钱币所占重量
        int n;
        scanf("%d",&n);
        for(int i = 1;i <= n;i++){
           scanf("%d%d",&list[i].v,&list[i].w);
        }
        for(int i = 0;i <= s;i++){
           dp[i] = INF;
        }
        dp[0] = 0;
 //因为要求所有物品恰好装满,所以初始时,除dp[0]外,其余dp[j]均为无穷(或者不存在)
        for(int i = 1;i <= n;i++){//遍历所有物品
           for(int j = list[i].w;j <= s;j++){//遍历所有可能转移的情况
              if(dp[j-list[i].w] != INF)//若不为不穷,就可以用此状态转移而来
                 dp[j] = min(dp[j],dp[j-list[i].w] + list[i].v);
              }
           }
           if(dp[s] != INF)//若存在一种方案使背包恰好装满,输出其最小值
              printf("The minimum amount of money in the piggy-bank is %d.\n",dp[s]);
           else //弱不存在方案
              puts("This is impossible.");
          }
          return 0;
}

问题9:为了挽救灾区同胞的生命,心系灾区同胞的曾sir准备自己采购一些粮食支援灾区,现在假设曾sir一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:曾sir用有限的资金最多能才够多少公斤粮食?

分析:对每个物品的总数量进行了限制,即多重背包问题。对每种物品进行拆分,使物品数量大大减少,同时通过拆分后的物品间的组合又可以组合出所有物品数量的情况

#include<stdio.h>
struct E{
     int w;//价格
     int v;//重量
}list[2001];
int dp[101];
int max(int a,int b){return a > b ? a:b}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
         int s,n;
         scanf("%d%d",&s,&n);
         int cnt = 0;//拆分后物品总重量
         for(int i = 1;i <= n;i++){
            int v,w,k;
            scanf("%d%d%d",&w,&v,&k);
            int c = 1;
            while(k-c > 0){//对输入的数字k,拆分成1,2,4...k-2^c + 1,c为使最后一项大于0的最大整数
                 k -= c;
                 list[++cnt],w = c*w;
                 list[cnt].v = c*v;//拆分后的大米重量和价格均为组成该物品的大米的重量价格和
                 c *= 2;
            }
            list[++cnt].w = w*k;
            list[cnt].v = v*k;
         }
         for(int i = 1;i <= s;i++) dp[i] = 0;//初始值
         for(int i = 1;i <= cnt;i++){//对拆分后的所有物品进行0-1背包
            for(int j = a;j >= list[i].w;j--){
               dp[j] = max(dp[j],dp[j-list[i].w] + list[i].v);
            }
         }
         printf("%d\n",dp[s]);
     }
     return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值