【9921】暗黑破坏神
Time Limit: 1 second
Memory Limit: 128 MB
【问题描述】
游戏的主人公有 n 个魔法每个魔法分为若干个等级,第 i 个魔法有 p[i] 个等级 ( 不包括 0) 每个魔法的每个等级都有一个效果值,一个 j 级的 i 种魔法的效果值为 w[i][j] 魔法升一级需要一本相应的魔法书 购买魔法书需要金币,第 i 个魔法的魔
法书价格为 c[i] 而小 x 只有 m 个金币 ( 好孩子不用修改器 ) 你的任务就是帮助小 x 决定如何购买魔法书才能使所有魔
法的效果值之和最大 开始时所有魔法为 0 级 效果值为 0。
【输入格式】
第一行 用空格隔开的两个整数 n,m,
以下 n 行 描述 n 个魔法,
第 i+1 行描述 第 i 个魔法。
格式如下
c[i] p[i] w[i][1] w[i][2] ... w[i][p[i]]
0<n≤100,0<m≤500,0<p[i]≤50,0<c[i]≤10
【输出格式】
第一行输出一个整数,即最大效果值。
以后 n 行输出你的方案:
第 i+1 行有一个整数 v[i] 表示你决定把第 i 个魔法学到 v[i] 级
如果有多解 输出花费金币最少的一组
如果还多解 输出任意一组
Sample Input
3 10 1 3 1 2 2 2 3 2 4 6 3 3 2 1 10
Sample Output
11 1 0 3
【题解】
说下。学到了3即,就只能得到a[i][3]那个对应的效果值。a[i][1],a[i][2]并没有累加到a[i][3]。也就是说如果a[i][1] = 1,a[i][2] =3,a[i][3] = 5,学到3级,就只有5点效果值。而不是8点。
然后这就是一个多重背包,然后记录方案数,输出方案的问题了。难点在输出方案。
如果用一维数组f来做这个问题,当然是可做的了。但是方案会很难记录。
所以我用了二维的数组。f[i][j]
表示前i种魔法,金币使用不超过j所能获得的最大效果值。
f[i][j] = max(f[i-1][j],f[i-1][j-k*w[i]]+c[k]);
然后我们在做的时候,用一个what数组,记录下我们的决策是什么。
如果我们更新了一个解,就要更新相应的what[i][j];
其中what是一个三维数组what[i][j][0]和what[i][j][1],。。。。好像第一维没用。
what[i][j][0]是选了哪一个魔法(肯定就是i啦。所以说第一维没用),然后what[i][j][1]表示i魔法学到了几级。。 //后记:第一维如果不学就为-1,所以可以起判断作用,所以还是有用的。
输出f[n][m];
最后用while循环就能输出方案了。知道学了几级,就记录学了几级就可以了。最后输出。
还有金币较小问题。
如果f[n][m] == f[n][m-1]则递减m。最后输出f[n][m]
还记得f[i][j]的含义吗?表示前i种魔法,金币使用不超过j所能获得的最大效果值。
这样应该能理解了吧?
【代码】
#include <cstdio> #include <cstring> int n,m,w[101],num[101],c[101][51],f[101][501],what[101][501][2],le[101] = {0}; void input_data() { memset(c,0,sizeof(c)); scanf("%d%d",&n,&m); for (int i = 1;i <= n;i++) // 先输入花费,然后是 能学到等级以及学到几级能获得多少效果 { scanf("%d%d",&w[i],&num[i]); for (int j = 1;j <= num[i];j++) scanf("%d",&c[i][j]); } } void get_ans() { memset(f,0,sizeof(f)); //先初始化f数组 for (int i = 1;i <= n;i++) { for (int j = m;j >=0;j--) //倒序进行,因为是多重背包。和0/1背包类似 { f[i][j] = f[i-1][j]; //no learn what[i][j][0] = -1; //因为不学,所以记录学的魔法为-1,诶好像一维也有用啊 for (int k = 1;k <= num[i];k++) //枚举学到了几级。 { if (k*w[i] > j) break; //如果超过了容量则跳过 if (f[i][j] < f[i-1][j-k*w[i]] + c[i][k]) //如果能更新解 则更新 { f[i][j] = f[i-1][j-k*w[i]] + c[i][k]; what[i][j][0] = i; //因为更新了解,所以相应的方案信息也要记录 what[i][j][1] = k; } } } } } void output_ans() { int mm = m; while (f[n][mm] == f[n][mm-1]) mm--; //这是最大效果相同然后金币要尽量少的实现方式 printf("%d\n",f[n][mm]); //先输出最大效果 int tt = what[n][mm][0],kk = what[n][mm][1],i = n; while (tt!=0) //如果金币还有剩余,则还有魔法信息没有扫描到 { if (tt!=-1) //如果不是跳过这个魔法不学 { le[tt]+=kk; //这个魔法等级增加 int needjian = kk*w[i]; //这是i魔法花费的金币,这样可以找到上一个魔法的信息 mm -= needjian; //跳到上一层的决策 } i--; // 跳回上一个决策 tt = what[i][mm][0]; kk = what[i][mm][1]; } for (int i = 1;i <= n;i++) printf("%d\n",le[i]); //输出每个魔法的等级即可。 } int main() { //freopen("F:\\rush.txt","r",stdin); input_data(); get_ans(); output_ans(); return 0; }