You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1252. 搭配购买

一、题目描述

Joe觉得云朵很美,决定去山上的商店买一些云朵。

商店里有 n 朵云,云朵被编号为 1,2,…,n,并且每朵云都有一个价值。

但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。

但是Joe的钱有限,所以他希望买的价值越多越好

输入格式1 行包含三个整数 nmw,表示有 n 朵云,m 个搭配,Joew 的钱。

2n+1行,每行两个整数 c_id_i 表示 i 朵云的价钱和价值。

n+2n+1+m 行,每行两个整数 u_iv_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];       // 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,肯定不会在内存上出问题。

总结 101背包一维相比二维,能够节约非常大的空间,二维特别容易MLE201背包一维相比二维,不用考虑上一个依赖是不是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;
}