洛谷 P1776 宝物筛选(二进制优化&单调队列优化)


题目链接:宝物筛选

题目描述

终于,破解了千年的难题。小 F 找到了王室的宝物室,里面堆满了无数价值连城的宝物。

这下小 F 可发财了,嘎嘎。但是这里的宝物实在是太多了,小 F 的采集车似乎装不下那么多宝物。看来小 FF 只能含泪舍弃其中的一部分宝物了。

F 对洞穴里的宝物进行了整理,他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值,之后开始了宝物筛选工作:小 F 有一个最大载重为 w 的采集车,洞穴里总共有 nn 种宝物,每种宝物的价值为 v_i​,重量为 w_i ​,每种宝物有 m_i 件。小 F 希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。

输入格式

第一行为一个整数 n 和 W,分别表示宝物种数和采集车的最大载重。

接下来 n 行每行三个整数 v_i,w_i,m_i​。

输出格式

输出仅一个整数,表示在采集车不超载的情况下收集的宝物的最大价值。

输入输出样例

输入 

4 20
3 9 3
5 9 1
9 4 2
8 1 3

输出 

47

说明/提示

对于 30\% 的数据,n\leq \sum m_i\leq 10^40\le W\leq 10^3

对于 100\% 的数据,n\leq \sum m_i \leq 10^50\le W\leq 4\times 10^41\leq n\le 100


思路

这题没什么好说的,一道典型的多重背包题目,就是数据太大了,要是套模板的话肯定会爆,所以就需要用到二进制优化或单调队列优化。


二进制优化

二进制优化就是把数量依次分解为若干个二进制数和剩下的余数,而这些数可以组合表示1~m中的任意一个整数,通过这样的方式,等于把我们的物品的总数变多了,然而每个物品的数量都是1,最后动态规划的时候就相当于做01背包了

时间上,原本是数量从1到m进行枚举,优化后变成了从2^0乘2枚举,时间大大缩短了。那么就直接上代码。

代码

/*************************************************
Note:二进制优化
*************************************************/
#include <queue>
#include <stack>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <iomanip>
#include <string.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
int n, m, ans, cnt = 1;
int f[N];
int w[N], v[N];
int main()
{
    int a, b, c;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d%d%d", &a, &b, &c);
        for (int j = 1; j <= c; j <<= 1)
        {
            v[++cnt] = j * a, w[cnt] = j * b;
            c -= j;
        }
        if (c)
        {
            v[++cnt] = a * c, w[cnt] = b * c;
        }
    }
    for (int i = 1; i <= cnt; ++i)
    {
        for (int j = m; j >= w[i]; --j)
        {
            f[j] = max(f[j], f[j - w[i]] + v[i]);
        }
    }
    printf("%d\n", f[m]);
    return 0;
}

到这里这道题二进制优化就解决了,但是还有一种单调队列优化的思路我也讲一下。

单调队列优化

我们知道状态转移方程: 

f[i][j]=max(f[i−1][j−w∗k]+v∗k);(k<=c)

现在我们要把这个方程变成一个单调队列可以优化的形式,于是我们假设:d=j mod w[i],s=⌊jw[i]⌋

f[i][j]=max(f[i−1][d+w∗k]−v∗k)+v∗s(s-k<=c) 

之后我们枚举余数d,然后对于每个余数d都用单调队列优化即可。上代码!

代码

/*************************************************
Note:单调队列优化
*************************************************/
#include <queue>
#include <stack>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <iomanip>
#include <string.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;
inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
int n, V, ans, head, tail;
int q[N], q2[N], dp[N];
int main()
{
    scanf("%d%d", &n, &V);
    for (int i = 1; i <= n; i++)
    {
        int v, w, c;
        scanf("%d%d%d", &w, &v, &c);
        if (v == 0)
        {
            ans += w * c;
            continue;
        }
        int k = V / v;
        c = min(c, k);
        for (int d = 0; d < v; d++)
        {
            head = tail = 0;
            k = (V - d) / v;
            for (int j = 0; j <= k; j++)
            {
                while (head < tail && dp[d + j * v] - j * w >= q2[tail - 1])
                    tail--;
                q[tail] = j;
                q2[tail++] = dp[d + j * v] - j * w;
                while (head < tail && q[head] < j - c)
                    ++head;
                dp[d + j * v] = max(dp[d + j * v], q2[head] + j * w);
            }
        }
    }
    printf("%d", ans + dp[V]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

꧁Q༒ོγ꧂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值