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\n",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\n",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\n",Solve());
32     }
33     return 0;
34 }
View Code

  总结:

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

    设成几维的?

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

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

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

 

posted @ 2018-11-03 10:39  HHHyacinth  阅读(430)  评论(0编辑  收藏  举报