题解「luoguP1064」金明的预算方案
Posted lpyaxofficial
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解「luoguP1064」金明的预算方案相关的知识,希望对你有一定的参考价值。
之前在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」金明的预算方案的主要内容,如果未能解决你的问题,请参考以下文章