1. 程式人生 > >hdu 2159FATE(完全揹包)

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變數很重要,要設成幾維的以及含義。

    設成幾維的?

      有多少個限制條件,就設定成幾維的,例如此題有三個限制條件①怪物種類②忍耐度③殺怪數

      如果不適用滾動陣列,則需要設定成三維陣列。

      如果使用滾動陣列優化空間,則把第一個限制條件開闢的空間省去了,但第一個限制條件要在最外層迴圈處