zoukankan      html  css  js  c++  java
  • hdu 2159FATE(完全背包)

    传送门

    https://www.cnblogs.com/violet-acmer/p/9852294.html

    题解:

      思路一:完全背包转“01”背包

        考虑到第ki个怪最多杀min(m/b[ki],s)个,于是可以把第ki个怪转化为min(m/b[ki],s)个忍耐度及经验值均不变的怪,然后求解这个01背包问题。

        (1):不用滚动数组优化

          本题有三个限制条件①怪物种类②忍耐度③杀怪数。

          如果不使用滚动数组优化空间,则需要开个三维数组dp[ maxMaster ][ max_m ][ max_s ]。

          dp[ tot ][ i ][ j ]的含义是杀第tot个怪时,耗费 i 个忍耐度和 j 个杀怪数所获得的最大经验值。  

     1 void Solve()
     2 {
     3     int tot=0;//把所有的 ki 怪转化为min(s,m/b[ki])个忍耐度及经验值均不变的物品时的总个数
     4     for(int kind=1;kind <= k;++kind)
     5     {
     6         int x=min(s,m/b[kind]);//第 ki 个怪最多可转化成 x 个
     7         while(x--)//将这 x 依次加入到背包中 
     8         {
     9             for(int i=1;i <= m;++i)//当前耗费的忍耐度
    10                 for(int j=1;j <= s;++j)//当前杀怪数
    11                     if(i >= b[kind])
    12                         dp[tot][i][j]=max(dp[tot-1][i][j],dp[tot-1][i-b[kind]][j-1]+a[kind]);
    13                     else
    14                         dp[tot][i][j]=dp[tot-1][i][j];
    15             tot++;
    16         }
    17     }
    18 }
    View Code

          思路完全正解,提交试试,返回的结果竟然是MLE...............

        (2):使用滚动数组优化

          既然MLE,那我用滚动数组优化一下总行了吧

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 using namespace std;
     5 #define mem(a,b) memset(a,b,sizeof(a))
     6 const int maxn=100+50;
     7 
     8 int n,m,k,s;
     9 int a[maxn],b[maxn];
    10 int dp[maxn][maxn];
    11 
    12 int Solve()
    13 {
    14     mem(dp,0);
    15     bool index=1;
    16     for(int kind=1;kind <= k;++kind)
    17     {
    18         int x=min(m/b[kind],s);
    19         while(x--)//x 个 ki 怪物
    20         {
    21             for(int i=m;i >= b[kind];--i)
    22                 for(int j=1;j <= s;++j)
    23                     dp[i][j]=max(dp[i][j],dp[i-b[kind]][j-1]+a[kind]);
    24         }
    25     }
    26     int res=m+1;
    27     for(int i=1;i <= m;++i)
    28         for(int j=1;j <= s;++j)
    29             if(dp[i][j] >= n)
    30                 res=(res > i ? i:res);//找到经验值达到n以上的最小的忍耐度
    31     return m-res;
    32 }
    33 
    34 int main()
    35 {
    36     while(~scanf("%d%d%d%d",&n,&m,&k,&s))
    37     {
    38         for(int i=1;i <= k;++i)
    39             scanf("%d%d",a+i,b+i);
    40         printf("%d
    ",Solve());
    41     }
    42 }
    View Code

          bingo,正解,不过,来分析一下此种做法的时间复杂度。

          对于最坏的情况,m=100,k=100,s=100,且对于所有的 i 有 a[i] = b[i] =1,其时间复杂度高达O(n^4),要不是此题范围小,指定超时。

          那么,还有比这更有的算法吗?

          有个稍加优化的方法,可以将最坏的时间复杂度变为O(n^3log(n))。

          把第ki个怪拆成忍耐度为b[ki]*(2^x)、经验值为a[ki]*(2^x)的若干个怪,其中 x 满足 b[ki]*(2^x) < m && (2^x) < s 。

          这是二进制的思想,因为不管最优策略杀几头第 ki 个物品,总可以表示成若干个 2^x 个怪物的和。

          这样把每头怪拆成O( log(min(m/b[kind],s)) )头怪,是一个很大的改进。  

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cmath>
     5 using namespace std;
     6 #define mem(a,b) memset(a,b,sizeof(a))
     7 const int maxn=100+50;
     8 
     9 int n,m,k,s;
    10 int a[maxn],b[maxn];
    11 int dp[maxn][maxn];
    12 
    13 int Solve()
    14 {
    15     mem(dp,0);
    16     bool index=1;
    17     for(int kind=1;kind <= k;++kind)
    18     {
    19         int x=log(min(m/b[kind],s))/log(2);
    20         for(int tot=0;tot <= x;++tot)
    21         {
    22             for(int i=m;i >= (1<<tot)*b[kind];--i)
    23                 for(int j=(1<<tot);j <= s;++j)
    24                     dp[i][j]=max(dp[i][j],dp[i-(1<<tot)*b[kind]][j-(1<<tot)]+(1<<tot)*a[kind]);
    25         }
    26     }
    27     int res=m+1;
    28     for(int i=1;i <= m;++i)
    29         for(int j=1;j <= s;++j)
    30             if(dp[i][j] >= n)
    31                 res=(res > i ? i:res);//找到经验值达到n以上的最小的忍耐度
    32     return m-res;
    33 }
    34 
    35 int main()
    36 {
    37     while(~scanf("%d%d%d%d",&n,&m,&k,&s))
    38     {
    39         for(int i=1;i <= k;++i)
    40             scanf("%d%d",a+i,b+i);
    41         printf("%d
    ",Solve());
    42     }
    43 }
    View Code

      思路二:完全背包+滚动数组优化空间

        设dp[i][j]表示消耗 i 个忍耐度,杀 j 头怪所获得的最大经验值。

        状态转移方程:

          dp[i][j]=max(dp[i][j],dp[i-b[k1]][j-1]+a[k1])

          dp[i-b[k1]][j-1]+a[k1] : 杀k1怪所获得最大经验值

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 using namespace std;
     5 #define mem(a,b) memset(a,b,sizeof(a))
     6 const int maxn=100+50;
     7 
     8 int n,m,k,s;
     9 int a[maxn],b[maxn];
    10 int dp[maxn][maxn];//dp[i][j] : 所需耐力值为i杀怪数为j时所获得的最大经验值
    11 
    12 int Solve()
    13 {
    14     mem(dp,0);
    15     for(int k1=1;k1 <= k;++k1)
    16         for(int i=b[k1];i <= m;++i)
    17             for(int j=1;j <= s;++j)
    18                 dp[i][j]=max(dp[i][j],dp[i-b[k1]][j-1]+a[k1]);
    19     for(int i=1;i <= m;++i)
    20         for(int j=1;j <= s;++j)
    21             if(dp[i][j] >= n)
    22                 return m-i;
    23     return -1;
    24 }
    25 int main()
    26 {
    27     while(scanf("%d%d%d%d",&n,&m,&k,&s) != EOF)
    28     {
    29         for(int i=1;i <= k;++i)
    30             scanf("%d%d",a+i,b+i);
    31         printf("%d
    ",Solve());
    32     }
    33     return 0;
    34 }
    View Code

      总结:

        这种题设dp变量很重要,要设成几维的以及含义。

        设成几维的?

          有多少个限制条件,就设置成几维的,例如此题有三个限制条件①怪物种类②忍耐度③杀怪数

          如果不适用滚动数组,则需要设置成三维数组。

          如果使用滚动数组优化空间,则把第一个限制条件开辟的空间省去了,但第一个限制条件要在最外层循环处

  • 相关阅读:
    Windows下IIS+PHP 5.2的安装与配置
    windows下安装、卸载mysql服务
    电脑变绿色
    libmysql.dll是否真的要拷贝到c:\windows目录下呢?
    用PHPnow搭建PHP+MYSQL网站开发环境
    VPS初始化及Nginx+MySQL+PHP/PHPMyAdmin安装优化cnblogs
    二级域名三级域名设置方法
    一句简单命令重启nginx
    上海世博会门票
    无法载入 mysql 扩展
  • 原文地址:https://www.cnblogs.com/violet-acmer/p/9899946.html
Copyright © 2011-2022 走看看