码迷,mamicode.com
首页 > 其他好文 > 详细

【题解】「luoguP1064」金明的预算方案

时间:2020-11-01 10:22:31      阅读:11      评论:0      收藏:0      [点我收藏+]

标签:状态   操作   algorithm   pre   空间   它的   空间复杂度   方式   附件   

之前在luogu上写的

仍然作为学习记录使用

这道题涉及的是背包问题,DP的一种模型。
由于我之前没有发过背包相关的题解,所以先简单讲一下。

01背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

这时我们开一个数组叫d[i][j],表示前i个物品放到容量为j的背包中可获得的最大价值。

然后得出状态转移方程:
如果可选(即把这个东西放进去背包不会被撑爆)
d[i][j]=max(d[i-1][j],d[i-1][j-c[i]]+w[i])
解释一下,就是:

如果可选,则前i个物品放到容量为j的背包中可获得的最大价值是此时不放i和放i中的最大值。
那么写个循环枚举:

for(int i=0;i<=n;i++)
for(int j=0;j<=V;j++)
if(放得开)
d[i][j]=max(d[i-1][j],d[i-1][j-c[i]]+w[i])

滚动数组优化空间复杂度

我们在枚举时发现,在涉及d的操作时,只涉及d[i][j]和d[i][j-1],而不涉及d[i][j+1],

因此,我们用一张图来描述一下他的思路:
(@代表数据,#代表正在更新的位置,O代表需要用的数据)
优化前
0 1 2 3 4(j)
1 @ @ O O @
2 @ @ @ # @
3 @ @ @ @ @
4 @ @ @ @ @
(i)
优化后
0 1 2 3 4(j)推进方向<-
  @ @ O O#

这样我们就吧数组压到了一维。

聪明人已经明白了还不明白可以去看背包九讲。

下面开始看题

这是有依赖的背包问题,但这个题仍可使用01背包的方式处理。

题目说想买附件必须有主件,所以我们可以在输入时把主件的附件记下来。
由于题目说附件最多两个,且附件不再有从属于自己的附件出现,所以便可像下面那样记录物品:

struct node
{
	int v;//价格(体积)
	int p;//按题设乘积后的价值
	int pd;//价值等级
	int q; //它的主件
	int f1;//它的附件一
	int f2;//附件二
}a[100];

其中,pd,f1,f2可以在输入时一并写入,下面给出输入代码。

	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &a[i].v, &a[i].pd, &a[i].q);
		if (a[i].q != 0)//如果是附件
		{//在其主件上做上标记(写入f1,f2)
			if(a[a[i].q].f1==0)
				a[a[i].q].f1 = i;
			else 
				a[a[i].q].f2 = i;
		}
		a[i].p = a[i].pd * a[i].v;//计算价值
	}

物品已经记录好了,下面开始尝试写状态转移方程:

附件是不能单独选的,所以我们只考虑主件的情况。

1.只选主件
2.选主件和附件一
3.选主件和附件二
4.选主件和两个附件

那么我们可以得到四个状态转移方程,分别对应上面四种情况

1.d[j]=max(d[j],d[j-a[i].v]+a[i].p)
2.d[j]=max(d[j],d[j-a[i].v-a[F1].v]+a[i].p+a[F1].p)
3.d[j]=max(d[j],d[j-a[i].v-a[F2].v]+a[i].p+a[F2].p)
4.d[j]=max(d[j],d[j-a[i].v-a[F1].v-a[F2].v]+a[i].p+a[F1].p+a[F2].p)

这里F1,F2是宏名,指a[i]的附件的编号。

这时我们就可以写递推了:

//d[j]即容量为j时能放下的最大价值
for (i = 0; i <= m; i++)
	for (j = n; j >0; j--)
	{
		if (a[i].q == 0 && a[i].v <= j)
			d[j] = max(d[j], d[j - a[i].v] + a[i].p);
		if (a[i].q == 0 && a[i].v+a[F1].v <= j)
			d[j] = max(d[j], d[j - a[i].v - a[F1].v] + a[i].p + a[F1].p);
		if (a[i].q == 0 && a[i].v + a[F2].v <= j)
			d[j] = max(d[j], d[j - a[i].v - a[F2].v] + a[i].p + a[F2].p);
		if (a[i].q == 0 && a[i].v + a[F2].v+a[F1].v <= j)
			d[j] = max(d[j], d[j - a[i].v - a[F1].v - a[F2].v] + a[i].p + a[F1].p + a[F2].p);
		
	}

(这里我们会发现如果遇到附件则不执行任何操作,为什么能这样?滚动数组)

最后输出d[n]就可以了。

下面给出完整代码:

#include<cstdio>
#include<algorithm>
#define F1 a[i].f1
#define F2 a[i].f2
using namespace std;
int n, m;
int d[500000];//d[j]即容量为j时能放下的最大价值
struct node
{
	int v;
	int p;
	int pd;
	int q;
	int f1;
	int f2;
}a[100];
int main()
{
	int i, j;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &a[i].v, &a[i].pd, &a[i].q);
		if (a[i].q != 0)
		{
			if(a[a[i].q].f1==0)
				a[a[i].q].f1 = i;
			else 
				a[a[i].q].f2 = i;
		}
		a[i].p = a[i].pd * a[i].v;
	}
	for (i = 0; i <= m; i++)
	{
		for (j = n; j >0; j--)
		{
			if (a[i].q == 0 && a[i].v <= j)
				d[j] = max(d[j], d[j - a[i].v] + a[i].p);
			if (a[i].q == 0 && a[i].v+a[F1].v <= j)
				d[j] = max(d[j], d[j - a[i].v - a[F1].v] + a[i].p + a[F1].p);
			if (a[i].q == 0 && a[i].v + a[F2].v <= j)
				d[j] = max(d[j], d[j - a[i].v - a[F2].v] + a[i].p + a[F2].p);
			if (a[i].q == 0 && a[i].v + a[F2].v+a[F1].v <= j)
				d[j] = max(d[j], d[j - a[i].v - a[F1].v - a[F2].v] + a[i].p + a[F1].p + a[F2].p);
		}
	}
	printf("%d", d[n]);
}

谢谢观赏

【题解】「luoguP1064」金明的预算方案

标签:状态   操作   algorithm   pre   空间   它的   空间复杂度   方式   附件   

原文地址:https://www.cnblogs.com/lpyaxofficial/p/13907065.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!