跳转至

混合背包

混合背包,就是将各种背包混合后的背包模型。

有 N 种物品和一个容量是 V 的背包。物品一共有三类,每种体积是 vi,价值是 wi。

第一类物品只能用1次(01背包); 第二类物品可以用无限次(完全背包); 第三类物品最多只能用 si 次(多重背包);

上面三种情况其实都是完全背包,很好理解。

有些时候,混合背包还会和分组背包结合,具体可以看例题。

例题 #1

有N个物品,物品个数为Ai(Ai为0则无个数限制),价值为Wi,体积为Ci,给出G组限制,每组中的物品最多只能取一种,问物品体积刚好为D时,最大价值是多少

输入格式

第一行两个整数N和 D。 接下来N行,每行3个整数Ai,Wi,Ci 。 第N+2行一个非负整数 G 。 接下来G行,开头一个整数L,表示组的大小,然后L个整数,表示该组的物品编号,保证每个物品最多出现在一组中。

输出格式

输出一个整数表示最大的价值。若最大价值为负或无法满足体积恰好为V,则输出“i'm sorry...”。


/*                                                                                
                      Keyblinds Guide
                    ###################
      @Ntsc 2024

      - Ctrl+Alt+G then P : Enter luogu problem details
      - Ctrl+Alt+B : Run all cases in CPH
      - ctrl+D : choose this and dump to the next
      - ctrl+Shift+L : choose all like this
      - ctrl+K then ctrl+W: close all
      - Alt+la/ra : move mouse to pre/nxt pos'

*/
#include <bits/stdc++.h>
#include <queue>
using namespace std;

#define rep(i, l, r) for (int i = l, END##i = r; i <= END##i; ++i)
#define per(i, r, l) for (int i = r, END##i = l; i >= END##i; --i)
#define pb push_back
#define mp make_pair
#define int long long
#define pii pair<int, int>
#define ps second
#define pf first

// #define innt int
// #define inr int
// #define mian main
// #define iont int

#define rd read()
int read(){
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
      xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}

#define ell dbg('\n')
const char el='\n';
const bool enable_dbg = 1;
template <typename T,typename... Args>
void dbg(T s,Args... args) {
    if constexpr (enable_dbg){
    cerr << s;
    if(1)cerr<<' ';
        if constexpr (sizeof...(Args))
            dbg(args...);
    }
}

#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }
#else
#define dbg(...)
#endif


const int N = 3e5 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;


int f[N];
int vis[N];
vector<int> e[N];

int a[N],w[N],c[N];


void solve(){
    int n=rd,v=rd;
    for(int i=1;i<=n;i++){
        a[i]=rd,w[i]=rd,c[i]=rd;
        if(!a[i])a[i]=INF;
    }   
    int q=rd;
    for(int i=1;i<=q;i++){
        int l=rd;
        while(l--){
            int a=rd;
            e[i].push_back(a);
            vis[a]=1;
        }
    }


    for(int i=1;i<=n;i++){
        if(vis[i])continue;
        e[++q].push_back(i);//将为未分组的都独立分组
    }


    for(int i=0;i<N;i++)f[i]=-INF;
    f[0]=0;

    for(int i=1;i<=q;i++){
        for(int j=v;j;j--){
            for(auto k:e[i]){
                //对于每一组都独立考虑

                for(int l=1;l<=a[k]&&l*c[k]<=j;l++){
                    if(f[j-l*c[k]]==-INF)continue;
                    f[j]=max(f[j],f[j-l*c[k]]+l*w[k]);
                }
            }
        }

    }
    if(f[v]==-INF)cout<<"i'm sorry..."<<endl;
    else cout<<f[v]<<endl;
}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".in","w",stdout);

    int T=1;
    while(T--){
        solve();
    }
    return 0;
}

例题 #2 旅行商的背包

小 S 坚信任何问题都可以在多项式时间内解决,于是他准备亲自去当一回旅行商。在出发之前,他购进了一些物品。这些物品共有 \(n\) 种,第 \(i\) 种体积为 \(V_i\),价值为 \(W_i\),共有 \(D_i\) 件。他的背包体积是 \(C\)。怎样装才能获得尽量多的收益呢?作为一名大神犇,他轻而易举的解决了这个问题。

然而,就在他出发前,他又收到了一批奇货。这些货共有 \(m\) 件,第 \(i\) 件的价值 \(Y_i\) 与分配的体积 \(X_i\) 之间的关系为:\(Y_i=a_iX_i^2+b_iX_i+c_i\)。这是件好事,但小 S 却不知道怎么处理了,于是他找到了一位超级神犇(也就是你),请你帮他解决这个问题。

输入格式

第一行三个数 \(n,m,C\),如题中所述;

以下 \(n\) 行,每行有三个数 \(V_i,W_i,D_i\),如题中所述;

以下 \(m\) 行,每行有三个数 \(a_i,b_i,c_i\),如题中所述。

输出格式

仅一行,为最大的价值。

对于 \(100\%\) 的数据,\(1 \le n \le 10^4\)\(1 \le m \le 5\)\(1 \le C \le 10^4\),$ 1 \le W_i,V_i,D_i \le 1000\(,\)-1000 \le a_i,b_i,c_i \le 1000$。


考虑前面的多重背包使用二进制拆分,后面的完全背包因为完全不符合**背包物品的可加性**,所以只能暴力枚举。

要勤于计算时间复杂度。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define itn int
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
#define pf first 
#define ps second
#define cdbg(x) cerr<<#x<<" = "<<x<<' ';
#define rd read()
inline int read(){
    int x;
    cin>>x;
    return x;
}

const int N=5e5+5;
const int INF=1e18;
const int MOD=998244353;


/*

策略

主要来解决奇货问题
发现不适用于加法的法则了
即对于同一组abc
a(x+y)2+b(x+y)+c=
ax2+ay2+bx+by+c+2axy

所以不可以二进制分组
那么就直接枚举加多少呗!
但是未来减少枚举量,我们先把常规的背包做出来,再把这些加进去
*/


struct node{
    int v,w;
}t[N];

int tot;
int f[N];

void solve(){
    int n=rd,m=rd,c=rd;
    for(int i=1;i<=n;i++){
        int v=rd,w=rd,d=rd;
        int k=1;
        while(d>=k){
            t[++tot]={v*k,w*k};
            d-=k;
            k<<=1;
        }
        if(d)t[++tot]={v*d,w*d};
    }

    for(int i=1;i<=tot;i++){
        for(int j=c;j;j--){
            if(j-t[i].v>=0)f[j]=max(f[j],f[j-t[i].v]+t[i].w);
        }
    }

//  cdbg("OK");

    //先求出一部分
    for(int i=1;i<=m;i++){
        int a=rd,b=rd,cc=rd;
        for(int j=c;j;j--){
            for(int k=0;k<=j;k++){
                f[j]=max(f[j],f[j-k]+a*k*k+b*k+cc);
            }
        }
    }
    cout<<f[c]<<endl;
}

signed  main(){
    int T=1;
    while(T--){

        solve();
        // if(T)puts("");
    }
    return 0;
}