AcWing 1252. 搭配购买
\(AcWing\) \(1252\). 搭配购买
一、题目描述
\(Joe\)觉得云朵很美,决定去山上的商店买一些云朵。
商店里有 \(n\) 朵云,云朵被编号为 \(1,2,…,n\),并且每朵云都有一个价值。
但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。
但是\(Joe\)的钱有限,所以他希望买的价值越多越好。
输入格式
第 \(1\) 行包含三个整数 \(n,m,w\),表示有 \(n\) 朵云,\(m\) 个搭配,\(Joe\)有 \(w\) 的钱。
第 \(2∼n+1\)行,每行两个整数 \(c_i,d_i\) 表示 \(i\) 朵云的价钱和价值。
第 \(n+2∼n+1+m\) 行,每行两个整数 \(u_i,v_i\),表示买 \(u_i\) 就必须买 \(v_i\),同理,如果买 \(v_i\) 就必须买 \(u_i\)。
输出格式
一行,表示可以获得的最大价值。
数据范围
\(1≤n≤10000,0≤m≤5000,1≤w≤10000,1≤ci≤5000,1≤di≤100,1≤u_i,v_i≤n\)
输入样例:
5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2
输出样例:
1
二、解题思路
三、一维\(01\)背包解法
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, sum; //有 n 朵云,m 个搭配,Joe有 sum 的钱。
int v[N], w[N]; //表示 i 朵云的价钱和价值
int p[N];
int f[N];
//最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); //路径压缩
return p[x];
}
int main() {
cin >> n >> m >> sum;
//初始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
//读入每个云朵的价钱(体积)和价值
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
while (m--) {
int a, b;
cin >> a >> b; //两种云朵需要一起买
int pa = find(a), pb = find(b);
if (pa != pb) {
//集合有两个属性:总价钱、总价值,都记录到root节点上
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
// 注意:这里不能认为一维的能AC,二维的替代写法就一定能AC
// 这是因为这里的判断p[i]==i,导致i不一定是连通的,
// 所以f[i][j]=f[i-1][j]这句话就不一定对
// 所以,看来终极版本的01背包一维解法还是有一定价值的。
for (int i = 1; i <= n; i++)
if (p[i] == i) //只关心集合代表元素,选择一组
for (int j = sum; j >= v[i]; j--) //体积由大到小,倒序,01背包
f[j] = max(f[j], f[j - v[i]] + w[i]);
//输出最大容量下获取到的价值
printf("%d\n", f[sum]);
return 0;
}
四、二维\(01\)背包解法与不能\(AC\)的理解
采用二维数组的表示法,有以下两个问题:
-
本行结果不一定是从上一行推导过来,因为上一行很可能不是这个家族的族长,只有族长也有资格进行计算。
可以采用\(last\)变量记录的方法模拟完成二维数组的计算,具体实现见代码。 -
内存超界
过掉\(7/11\)个数据,无法\(AC\)
原因分析:\(f[N][N]\)第一维是可以选择的物品个数,上限是\(10000\);
第二维是可以支付的钱数,上限也是\(10000\);
如果按二维思路来处理,确实需要一个\(10000*10000\)的数组
\(10000*10000*8= 800000000 byte\)
\(800000000/1024/1024= 762MB\) 本题上限是\(64MB\),妥妥的超内存,\(MLE\)~
穷则思变,既然\(int\)+二维过不了,那么试试\(short\)吧,因为\(short\)最大是\(65536\),符合题意,并且只占两个\(bit\),就是\(10000*10000*2= 200000000 byte\)
\(200000000/1024/1024= 190MB\) 本题上限是\(64MB\),妥妥的超内存,\(MLE\)~
那么一维的为什么可以呢?
一维的只有\(10000*8=80000 byte\)
\(80000/1024/1024=0.076MB\) 本题上限是\(64MB\),肯定不会在内存上出问题。
总结:
(1)\(01\)背包一维相比二维,能够节约非常大的空间,二维特别容易\(MLE\)。
(2)\(01\)背包一维相比二维,不用考虑上一个依赖是不是\(i-1\)行的问题,不用特殊用\(last\)方式记录并处理,出错概率小
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, sum; //有 n 朵云,m 个搭配,Joe有 sum 的钱。
int v[N], w[N]; //表示 i 朵云的价钱和价值
int p[N];
int f[N][N];
/*过掉7/11个数据,无法AC*/
//最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); //路径压缩
return p[x];
}
int main() {
cin >> n >> m >> sum;
//实始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
//读入每个云朵的价钱(体积)和价值
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
while (m--) {
int a, b;
cin >> a >> b; //两种云朵需要一起买
int pa = find(a), pb = find(b);
if (pa != pb) {
//集合有两个属性:总价钱、总价值,都记录到root节点上
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
int last = 0;
for (int i = 1; i <= n; i++)
if (p[i] == i) { //因处理集合的代表元素
for (int j = 1; j <= sum; j++) {
f[i][j] = f[last][j];
if (v[i] <= j)
f[i][j] = max(f[i][j], f[last][j - v[i]] + w[i]);
}
last = i; //依赖的上一个状态
}
printf("%d\n", f[n][sum]);
return 0;
}