【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
",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
",le[i]); //输出每个魔法的等级即可。 
}

int main()
{
	//freopen("F:\rush.txt","r",stdin);
	input_data();
	get_ans();
	output_ans();
	return 0;
}


原文地址:https://www.cnblogs.com/AWCXV/p/7632379.html