|
|
|
|
##[$AcWing$ $1252$. 搭配购买](https://www.acwing.com/problem/content/description/1254/)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
$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$
|
|
|
|
|
|
|
|
|
|
**输入样例**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
5 3 10
|
|
|
|
|
3 10
|
|
|
|
|
3 10
|
|
|
|
|
3 10
|
|
|
|
|
5 100
|
|
|
|
|
10 1
|
|
|
|
|
1 3
|
|
|
|
|
3 2
|
|
|
|
|
4 2
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输出样例**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 二、解题思路
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
### 三、一维$01$背包解法
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#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]; // 01背包数组
|
|
|
|
|
|
|
|
|
|
// 最简并查集
|
|
|
|
|
int find(int x) {
|
|
|
|
|
if (p[x] != x) p[x] = find(p[x]); // 路径压缩
|
|
|
|
|
return p[x];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
scanf("%d %d %d", &n, &m, &sum);
|
|
|
|
|
// 初始化并查集
|
|
|
|
|
for (int i = 1; i <= n; i++) p[i] = i;
|
|
|
|
|
|
|
|
|
|
// 读入每个云朵的价钱(体积)和价值
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
scanf("%d %d", &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$方式记录并处理,出错概率小**
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#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;
|
|
|
|
|
}
|
|
|
|
|
```
|