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 forView Code(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}
思路完全正解,提交試試,返回的結果竟然是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變數很重要,要設成幾維的以及含義。
設成幾維的?
有多少個限制條件,就設定成幾維的,例如此題有三個限制條件①怪物種類②忍耐度③殺怪數
如果不適用滾動陣列,則需要設定成三維陣列。
如果使用滾動陣列優化空間,則把第一個限制條件開闢的空間省去了,但第一個限制條件要在最外層迴圈處