跳转至

特殊背包

二维背包

二维背包(二维01背包)就是在原01背包左右一个限制——重量的基础上,再添加一个限制——体积。

给定n nn种物品和一背包。物品i ii的重量是w_i,体积是b_i,其价值为v_i,背包的容量为c cc,容积为d dd。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

这时我们就需要在原有dp状态的基础上增加第3个维度。

设计dp状态为f_{i,j,k}表示可选物品为i,i+1,…,n;背包容量为j,背包容积为k时的二维01背包问题的最优值。在转移时延用01背包的转移方法,只不过需要判断两个条件。时间复杂度为O(nVM)。

优化上延用01背包优化方法,可以将第一维压去。

高维背包

高维背包并不是以多出限制的形式呈现,而是对多个物品由不同的数量限制。并且不同物品的数量通常≤5,这样我们可以在dp数组的每一个维度记录这个物品当前的数量

例题 #1 [USACO3.3] 商店购物 Shopping Offers

促销活动把一个或多个商品组合起来降价销售,例如:

三朵花的价格是 \(5\) 而不是 \(6\)\(2\) 个花瓶和一朵花的价格是 \(10\) 而不是 \(12\) 。 请编写一个程序,计算顾客购买一定商品的花费,尽量地利用优惠使花费最少。尽管有时候添加其他商品可以获得更少的花费,但是你不能这么做。

对于上面的商品信息,购买三朵花和两个花瓶的最少花费的方案是:以优惠价购买两个花瓶和一朵花(\(10\)),以原价购买两朵花(\(4\))。

输入格式

输入文件包括一些商店提供的优惠信息,接着是购物清单。(最多有 \(5\) 种商品)

第一行 优惠方案的种类数(\(0\leq s\leq99\))。

\(2\)\(\sim\)\(s+1\) 行 每一行都用几个整数来表示一种优惠方式。第一个整数 \(n\)\(\leq n\leq5\)),表示这种优惠方式由 \(n\) 种商品组成。后面 \(n\) 对整数 \(c\)\(k\) 表示 \(k\)\(1\leq k\leq5\))个编号为 \(c\)\(1\leq c\leq999\))的商品共同构成这种优惠,最后的整数 \(p\) 表示这种优惠的优惠价(\(1\leq p\leq9,999\))。优惠价总是比原价低。

\(s+2\) 行 这一行有一个整数 \(b\)\(0\leq b\leq5\)),表示需要购买 \(b\) 种不同的商品。

\(s+3\)\(\sim\)\(s+b+2\) 行 这 \(b\) 行中的每一行包括三个整数:\(c,k,p\)\(c\) 表示唯一的商品编号(\(1\leq c\leq999\)),\(k\) 表示需要购买的 \(c\) 商品的数量(\(1\leq k\leq5\))。\(p\) 表示 \(c\) 商品的原价(\(1\leq p\leq999\))。最多购买 \(5\times5=25\) 个商品。

输出格式

只有一行,输出一个整数:购买这些物品的最低价格。


#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<<'\n';
#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;


/*

策略

记录f_{q,w,e,r,t}表示买了5种商品的数目为 qwert时最小代价。把清单和商品都看作物品即可。
*/

int f[7][7][7][7][7];
int p[7],pp[7];
int ans=INF;
int id[N];
int amt[7];
int orp[7];
int b;
itn n;


vector<pii > e[120];
int v[N];

bool check(int x){

    for(int i=1;i<=b;i++){
        pp[i]=p[i];
    }
    for(auto v:e[x]){
        pp[v.pf]-=v.ps;
        if(pp[v.pf]<0)return 0;
    }
    return 1;
}

void dfs(int x){

    if(!x){
        for(int i=1;i<=n;i++){
            if(check(i)){
                // cdbg("OKK");
                // cdbg(i);
                // cerr<<p[1]<<p[2]<<pp[1]<<pp[2]<<endl;
                f[p[1]][p[2]][p[3]][p[4]][p[5]]
                    =min(f[p[1]][p[2]][p[3]][p[4]][p[5]],f[pp[1]][pp[2]][pp[3]][pp[4]][pp[5]]+v[i]);
            }
        }

        // cdbg("ok");

        int res=f[p[1]][p[2]][p[3]][p[4]][p[5]];
        for(int i=1;i<=b;i++){
            res+=(amt[i]-p[i])*orp[i];
        }
        ans=min(ans,res);


        return ;
    }

    for(int i=0;i<=amt[x];i++){
        p[x]=i;
        dfs(x-1);
    }

}

void solve(){
    n=rd;
    for(int i=1;i<=n;i++){
        int l=rd;
        for(int j=1;j<=l;j++){
            int a=rd,b=rd;
            e[i].pb(mp(a,b));
        }
        v[i]=rd;
    }

     b=rd;
    for(int i=1;i<=b;i++){
        id[rd]=i,amt[i]=rd,orp[i]=rd;
    }


    for(int i=1;i<=n;i++){
        for(int j=0;j<e[i].size();j++){
            e[i][j]=mp(id[e[i][j].pf],e[i][j].ps);
        }
    }

    // cerr<<e[1][0].pf<<e[1][1].pf<<endl;

    memset(f,0x3f3f,sizeof f);
    f[0][0][0][0][0]=0;

    dfs(b);

    // cdbg(f[1][2][0][0][0]);

    cout<<ans<<endl;


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

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

超大背包

类型 #1 容量大,物品大(选的物品不多)

[SNOI2017] 英雄联盟

正在上大学的小皮球热爱英雄联盟这款游戏,而且打的很菜,被网友们戏称为「小学生」。

现在,小皮球终于受不了网友们的嘲讽,决定变强了,他变强的方法就是:买皮肤!

小皮球只会玩 \(\text{N}\) 个英雄,因此,他也只准备给这 \(\text{N}\) 个英雄买皮肤,并且决定,以后只玩有皮肤的英雄。

\(\text{N}\) 个英雄中,第 \(\text{i}\) 个英雄有 \(K_i\) 款皮肤,价格是每款 \(C_i\) Q 币(同一个英雄的皮肤价格相同)。

为了让自己看起来高大上一些,小皮球决定给同学们展示一下自己的皮肤,展示的思路是这样的:对于有皮肤的每一个英雄,随便选一个皮肤给同学看。

比如,小皮球共有 5 个英雄,这 5 个英雄分别有 \(\text{0,0,3,2,4}\) 款皮肤,那么,小皮球就有 \(3 \times 2 \times 4 = 24\) 种展示的策略。

现在,小皮球希望自己的展示策略能够至少达到 \(\text{M}\) 种,请问,小皮球至少要花多少钱呢?

输入格式

第一行,两个整数 \(\text{N,M}\)

第二行,\(\text{N}\) 个整数,表示每个英雄的皮肤数量 \(K_i\)

第三行,\(\text{N}\) 个整数,表示每个英雄皮肤的价格 \(C_i\)

输出格式

一个整数,表示小皮球达到目标最少的花费。

共 10 组数据,第 \(\text{i}\) 组数据满足:\(\text{N} \le \max(5, \log_2^4i)\)\(150\)

\(\text{100}\%\) 的数据:\(\text{M} \le 10^{17}, 1 \le K_i \le 10, 1 \le C_i \le 199\)。保证有解。


我们发现这里的总贡献是乘。我们想到 \(f_i\)表示得到i种策略的最小代价,但是i太大了。

因此我们考虑**交换值域和定义域**

\(f_i\)为花费i元得到的最大策略数

#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<<'\n';
#define rd read()
inline int read(){
    int x;
    cin>>x;
    return x;
}

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


/*

策略

我们发现这里的总贡献是乘。我们想到f_i表示得到i种策略的最小代价,但是i太大了。
因此我们考虑交换值域和定义域
f_i为花费i元得到的最大策略数

*/

int amt[N];
int f[N];
int c[N];

void solve(){
    int n=rd,m=rd;
    for(int i=1;i<=n;i++){
        amt[i]=rd;
    }
    for(int i=1;i<=n;i++){
        c[i]=rd;
    }

    // memset(f,1,sizeof f);
    // cdbg(f[1]);

    for(int j=1;j<=n;j++){
        for(int i=N-1;i;i--){
            for(int k=amt[j];k;k--){
                if(i-c[j]*k>=0)f[i]=max(f[i],(f[i-c[j]*k]?f[i-c[j]*k]:1)*k);
                // else break;
            }
        }
    }
    int fg=0;
    for(int i=1;i<N;i++){
        if(f[i]>=m){
            cout<<i<<endl;
            fg=1;
            return ;
        }
    }
    assert(fg);



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

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

类型 #2 容量大 物品小 一个物品重复很多次

见同余最短路同余最短路

进程dp

例题 #1 [HNOI2001] 产品加工

题目描述

某加工厂有 A、B 两台机器,来加工的产品可以由其中任何一台机器完成,或者两台机器共同完成。由于受到机器性能和产品特性的限制,不同的机器加工同一产品所需的时间会不同,若同时由两台机器共同进行加工,所完成任务又会不同。

某一天,加工厂接到 \(n\) 个产品加工的任务,每个任务的工作量不尽一样。

你的任务就是:已知每个任务在 A 机器上加工所需的时间 \(t_1\),B 机器上加工所需的时间 \(t_2\) 及由两台机器共同加工所需的时间 \(t_3\),请你合理安排任务的调度顺序,使完成所有 \(n\) 个任务的总时间最少。

输入格式

第一行为一个整数 \(n\)

接下来 \(n\) 行,每行三个非负整数 \(t_1,t_2,t_3\),分别表示第 \(i\) 个任务在 A 机器上加工、B 机器上加工、两台机器共同加工所需要的时间。如果所给的时间 \(t_1\)\(t_2\)\(0\) 表示任务不能在该台机器上加工,如果 \(t_3\)\(0\) 表示任务不能同时由两台机器加工。

输出格式

仅一行一个整数,表示完成所有 \(n\) 个任务的最少总时间。

对于所有数据,有 \(1\le n\le 6\times 10^3\)\(0\le t_1,t_2,t_3\le 5\)


这类背包的特点是:f_{i,j,k}表示A花费i时间,B花费j时间,处理了前k个物品,是否可行。

压维,就是f_{j,k}B花费j时间,处理了前k个物品,在A上花费的最小时间。

本题可以把A,B共同成立的提到最前面,这样就不会在中间留下空隙了。

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

#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first
#define itn 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;
}

#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...); }


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

int a[N];
int b[N],c[N];
int f[2][N];

void solve(){
    int n=rd;
    for(int i=1;i<=n;i++){
        a[i]=rd;
        b[i]=rd;
        c[i]=rd;
    }
    int s=0;
    memset(f,0x3f3f,sizeof f);
    f[0][0]=0;
    for(int i=1;i<=n;i++){
        memset(f[i&1],0x3f3f,sizeof f[i&1]);
        s+=max(a[i],max(b[i],c[i]));
        for(int j=0;j<=s;j++){
            if(b[i])f[i&1][j]=min(f[i&1][j],f[i&1^1][j]+b[i]);
            if(a[i]&&j>=a[i])f[i&1][j]=min(f[i&1][j],f[i&1^1][j-a[i]]);
            if(c[i]&&j>=c[i])f[i&1][j]=min(f[i&1][j],f[i&1^1][j-c[i]]+c[i]);
        }
    }

    int ans=INF;
    for(int i=0;i<=s;i++){
        ans=min(ans,max(i,f[n&1][i]));
    }
    cout<<ans<<endl;


}

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

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