洛谷 P1455 搭配购买 题解
P1455 搭配购买
在此,我们先分析一下这道题目:
- 1 n个物品,每个物品有售价c[i],价值d[i],很容易联想到:每个物品只能选择一次。
- 2 m个搭配,根据题目描述:对于每个搭配,双向联通,且若选择其中一个则必须同时选择剩下所有的,即:所有相连的物品构成一个连通块(图论中无向图联通部分的概念)
- 3 我有w元,要求在使用钱数 <= w的情况下,取得最大价值,很容易联想到DP,并进一步想到:01背包(在有限空间里,对于有限的物品且每个物品只选择一次时取得最大价值的动态规划问题)
自此,我们开始思考如何实现上述的问题
- 问题1 如何求出连通块
- 问题2 如何实现01背包
- 问题3 如何解决问题1和问题2 (当我没说)
各个思考:
- 解决1 对于求连通块,我们可以很容易想到一个算法:tarjan但是对于这道题目特殊的形式,我们选择并查集
选择并查集原因:
- 1 我们并不需要求出每个店所在的联通块
- 2 我们使用整个块的信息来做01背包
- 3 很好维护,具体:每次合并u,v,我们都将u的信息给v,并相连,即:
合并与路径压缩
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
fa[fx]=fy;
c[fy]+=c[fx];
v[fy]+=v[fx];
find(x),find(y);
}
而我们如何实现并查集:
int x,y;
for (int i=1;i<=n;i++)
scanf ("%d%d",c+i,v+i),fa[i]=i; //初始化
for (int i=1;i<=m;i++){
scanf ("%d%d",&x,&y);
if (x<y) // 这句可以省略,实测:时间不会改变
swap(x,y);
if (find(x)!=find(y)) //判断是否可以合并(x与y的最高祖先是否相同)
merge(x,y);
}
本题,我用了路径压缩(讲道理只要写并查集都会写),为了防止单链结构卡常。
(透露一下:第二个测试点就是这种单链数据)
综上,问题1解决
- 解决2 我们可以在做完并查集后,可以做2个数组,存储每个最高祖先(即代表元)的代价与收益,然后对于这个线性结构做一遍现行的01背包
(开数组是以空间换时间对于本题开与不开均可)
自此,问题2解决
实现:
for (int i=1;i<=n;i++)
if (fa[i]==i)
co[++js]=c[i],
we[js]=v[i];
for (int i=1;i<=js;i++){
for (int j=w;j>=co[i];j--)
f[j]=max(f[j],f[j-co[i]]+we[i]);
}
printf ("%d\n",f[w]);
考虑各位观看完整代码需求:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int n,m,w;
int c[100005];
int v[100005];
int fa[100005];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
fa[fx]=fy;
c[fy]+=c[fx];
v[fy]+=v[fx];
find(x),find(y);
}
void init(){
scanf ("%d%d%d",&n,&m,&w);
int x,y;
for (int i=1;i<=n;i++)
scanf ("%d%d",c+i,v+i),fa[i]=i;
for (int i=1;i<=m;i++){
scanf ("%d%d",&x,&y);
if (x<y)
swap(x,y);
if (find(x)!=find(y))
merge(x,y);
}
return;
}
int co[100005];
int we[100005];
int js;
int f[100005];
void work(){
for (int i=1;i<=n;i++){
if (fa[i]==i)
co[++js]=c[i],
we[js]=v[i];
}
for (int i=1;i<=js;i++){
for (int j=w;j>=co[i];j--)
f[j]=max(f[j],f[j-co[i]]+we[i]);
}
printf ("%d\n",f[w]);
return;
}
int main(){
//freopen ("1455.in","r",stdin);
//freopen ("1455.out","w",stdout);
init();
work();
return 0;
}