洛谷 P3961 [TJOI2013]黄金矿工(分组背包,01背包的变形)

题目链接:

[TJOI2013]黄金矿工 - 洛谷洛谷 P3961 [TJOI2013]黄金矿工(分组背包,01背包的变形)https://www.luogu.com.cn/problem/P3961

思路:

是一个经过变形的01背包题,被挡住的金子不能立刻拿到,(用斜率判断是否在同一直线上,即可知道有没有被挡住),要把其前面的金子拿了才能拿。所以把他们放在一组处理(在代码实现上,需要用二维数组来装物品,一组的放一起)。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef  long long int ll;
const double eps = 1e-6; //斜率判断误差值
const int maxn = 1e5+5;
int n,m; //黄金个数,时间
vector<int> val[maxn]; //价值(分组后)
vector<int> cost[maxn]; //耗时(分组后)
int dp[maxn]; // dp[j]表示背包容量为j的时候能得到的最大val值
struct node{
    int x, y, t, v; //坐标,时间,价值
    double k; //斜率
} gold[maxn];

bool cmp(node a, node b){
    //斜率小的在前面,坐标小的在前面
    //斜率第一顺序,坐标第二顺序(这主要是为了让斜率相同的点排在一起,并且先拿到的放前面,方便后续处理)
    if(abs(a.k-b.k) < eps){ //斜率相等
        return a.x < b.x;
    }
    else return a.k < b.k;
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i=1; i<=n; i++){
        cin >> gold[i].x >> gold[i].y >> gold[i].t >> gold[i].v;
        gold[i].k = (double)gold[i].y / gold[i].x; //计算斜率
    }
    sort(gold+1, gold+n+1, cmp); //排序


    // 进行背包分组
    int idx = 1; //当前分组物品位置
    val[1].push_back(gold[1].v);
    cost[1].push_back(gold[1].t);
    double curK = gold[1].k;
    for(int i=2; i<=n; i++){
        if(abs(gold[i].k - curK) < eps){
            val[idx].push_back(gold[i].v);
            cost[idx].push_back(gold[i].t);
        }else{
            idx++;
            val[idx].push_back(gold[i].v);
            cost[idx].push_back(gold[i].t);
        }
    }

    //开始分组背包dp
    for(int i=1; i<=idx; i++){ //遍历物品(分组之后的)
        for(int j=m; j>=cost[i][0]; j--){ //遍历背包空间,能装下当前第一个物品才进入循环(是为了剪枝)
            int have = j; //目前剩余的空间
            int value = 0; //目前在这组物品里拿到的价值
            for(int k=0; k<cost[i].size(); k++){ //遍历这一组的所有物品
                if(have >= cost[i][k]){
                    have -= cost[i][k];
                    value += val[i][k];
                    dp[j] = max(dp[j], dp[have] + value);
                }
                else break; //当前物品拿不到,后面的也不能拿了
            }
        }
    }
    cout << dp[m] << endl;
    return 0;
}

看了别的大佬的代码,发现还有另一种处理方式,在分组的时候直接存储前缀和,代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef  long long int ll;
const double eps = 1e-6;
const int maxn = 1e5+5;
int n,m; //黄金个数,时间
vector<int> val[maxn]; //价值(分组后)
vector<int> cost[maxn]; //耗时(分组后)
int dp[maxn]; // dp[j]表示背包容量为j的时候能得到的最大val值
struct node{
    int x, y, t, v; //坐标,时间,价值
    double k; //斜率
} gold[maxn];

bool cmp(node a, node b){
    //斜率小的在前面,坐标小的在前面
    //斜率第一顺序,坐标第二顺序
    if(abs(a.k-b.k) < eps){ //斜率相等
        return a.x < b.x;
    }
    else return a.k < b.k;
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i=1; i<=n; i++){
        cin >> gold[i].x >> gold[i].y >> gold[i].t >> gold[i].v;
        gold[i].k = (double)gold[i].y / gold[i].x; //计算斜率
    }
    sort(gold+1, gold+n+1, cmp); //排序


    // 进行背包分组
    int idx = 1; //当前分组物品位置
    val[1].push_back(gold[1].v);
    cost[1].push_back(gold[1].t);
    double curK = gold[1].k;
    for(int i=2; i<=n; i++){
        if(abs(gold[i].k - curK) < eps){
            val[idx].push_back(gold[i].v);
            cost[idx].push_back(gold[i].t);
            //下面是前缀和处理
            int size = val[idx].size();
            if(size > 1){
                val[idx][size-1] += val[idx][size-2];
                cost[idx][size-1] += cost[idx][size-2];
            }
        }else{
            idx++;
            val[idx].push_back(gold[i].v);
            cost[idx].push_back(gold[i].t);
        }
    }

    //开始分组背包dp
    for(int i=1; i<=idx; i++){ //遍历物品
        for(int j=m; j>=cost[i][0]; j--){ //遍历背包空间,能装下当前第一个物品才进入循环
            for(int k=0; k<cost[i].size(); k++){
                if(j>=cost[i][k]) dp[j] = max(dp[j], dp[j-cost[i][k]] + val[i][k]);
            }
        }
    }
    cout << dp[m] << endl;
    return 0;
}

 

上一篇:CF1552B B. Running for Gold


下一篇:54. 螺旋矩阵(c++)