ZCMU 1919: kirito's 星爆氣流斬
Time Limit: 2 Sec Memory Limit: 128 MB
Description
主角kirito是使用世界首款完全潛行遊戲“刀劍神域(Sword Art Online)”的玩家。曾經很幸運的參與過封閉測試,並買下正式版的kirito,正準備體驗遊戲的第一次正式營運。但在登入後不久,kirito發現“登出”指令竟然消失,而與此同時自稱是SAO遊戲設計者“茅場晶彥”的人說:“無法完成攻略就無法離開遊戲,只有打倒位於“艾恩葛朗特”頂樓,第100層的頭目-達成“完全攻略”纔是離開這個世界唯一的方法。並且,在遊戲內GAME OVER或是嘗試脫下NERvGear,玩家會立刻被NERvGear發出的高頻率微波破壞腦部而死亡。”唯有接受這個矛盾事實的人,才能夠存活下去。
自己也被捲入其中的kirito,在遊戲的舞臺——巨大浮游城堡“艾恩葛朗特”裏,以不與人組隊的獨行劍士身份,逐漸嶄露頭角,並獲得“黑色劍士”的稱號。kirito以完全攻略的條件——到達城堡最上層爲目標,持續進行嚴酷且漫長的冒險,在這期間他邂逅了女性細劍使——“閃光”亞絲娜,以及公會“血盟騎士團團長”希茲克利夫,他的命運也一步步產生了巨大的變化。kirito能否從遊戲裏全身而退……
由於kirito是封弊者,kirito有一個二刀流技能,可以使用星曝氣流斬,斬殺了強大的守關BOSS。
但是星曝氣流斬需要很龐大的法力值。
現在商店有N個藥品,kirito的物品欄有W的容量。
第i個藥品有重量w_i,可以恢復法力值v_i,有數量c_i個。
現在請你幫助kirito計算他可以恢復的最大法力值。
Input
第一行兩個整數N,W(1 <= N <= 300,1 <= W <= 500000 )
接下來N行,每行三個整數w_i,v_i,c_i(1 <= w_i <= 10000,1 <= v_i <= 10000, 1 <= c_i <= 500)
Output
輸出一個整數
Sample Input
3 6 2 2 5 3 3 8 1 4 1
Sample Output
9
思路
這是一道典型的多重揹包轉爲01揹包題,但是在下手之前發現把所有藥品的數值都分開計入後遍歷打表時的時間複雜度會不堪入目,遂不知怎麼操作。後瞭解到多重揹包的二進制優化法,此題可解。
對於多重揹包問題除了用多重揹包解法,還能轉爲01揹包,將價值爲v,數量爲n的物品分爲n個單獨的物品錄入,用01揹包解法,此方法相比多重揹包標準解法會犧牲空間。但是若題目所給數據較大,用標準解法便會超時,此時轉換爲01揹包就顯得非常重要。通過對轉換後的01揹包進行二進制優化
二進制優化的原理:在二進制中,1,10,100,1000...,其對應十進制數1,2,4,8,16…。由於1,10,100,1000...可以拼出諸如1010,1001,1101等任意二進制數,一個二進制數對應一個十進制數,故其對應十進制數經過某種方式的搭配可組成任何十進制數。將01揹包中大量分散的相同物品整合爲數目1,2,4,8,...2^n,可以得出表示範圍內任意整數的該物品,繼而減少了揹包打表時遍歷消耗的時間,解決了01揹包標準解法和多重揹包標準解法超時的問題。
優化時用一個變量j來求得等比數列中的值,j<<1爲將x的二進制值左移一位,若x爲1,二進制值爲1,左移一位得到值10,代表2,再左移一位得到100,代表4,以此類推。在每次求得j值後將該物品總數Num切分出值爲j的一份,存入數組,此時計數變量n加一表示目前有n個數據存入,用於之後01揹包的打表。
二進制優化相關代碼段:
//二進制優化
for(int j = 1, n = 0; j < num; j <<= 1)//此處左移相當於j*=2
{
item[n] = { weight*j,value*j };
num -= j;
n++;
}
//如優化後還有數剩餘,則整體直接存入
if(num)
{
item[n]={ weight*num,value*num };
n++;
}
PS:本菜雞在做題時因爲第二個循環中漏寫n++導致WA......
AC代碼
#include <bits/stdc++.h>
using namespace std;
struct info
{
int w;
int v;
}item[500000];
int dp[500005];
int main()
{
//商店有N個物品,揹包有W的容量
//本題數據較大,將多重揹包轉化爲01揹包,防止TLE
int N, W;
int num;
int n;
scanf("%d%d", &N, &W);
num = 0;
for(int i=0;i<N;i++)
{
int weight, value, num;
scanf("%d%d%d", &weight, &value, &num);
//二進制優化
n = 0;
//j<<=1爲j=j左移一位得到的值,相當於j*2
for(int j=1;j<num;j<<=1)
{
item[n] = { weight*j,value*j };
num -= j;
n++;
}
//將二進制優化後剩餘的值加入列表
if(num)
{
item[n]={ weight*num,value*num };
n++;
}
//01揹包
//判斷第i個物品在放入揹包後容量爲j時比不放入揹包後容量爲j-item.w時價值是否更高
for (int i = 0; i < n; i++)
{
for (int j = W; j >= item[i].w; j--)
{
if (dp[j] < dp[j - item[i].w] + item[i].v)
dp[j] = dp[j - item[i].w] + item[i].v;
}
}
}
printf("%d\n", dp[W]);
return 0;
}
對揹包的理解還不是很透徹,導致寫揹包代碼段時浪費了不少時間,還需要學習。