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;
}
对揹包的理解还不是很透彻,导致写揹包代码段时浪费了不少时间,还需要学习。