背包问题的处理

背包问题的一些拓展与处理

装箱问题

题目描述:

有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积(正整数)。要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

核心思想

\(f[i]\)记录当体积为m时,最多能放多少

转移方程:\(f_j=max(f_j,f_{j-u}+u)\)

特殊性:此处的价值和容量是同一个东西

宠物小精灵之收服

题目描述

有一个背包,第一个容量是M,第二个容量是V,共有N个物品,问最多能装几个,此时第二个容量最多能剩多少

\(0<N≤1000\)
\(0<M≤500\)
\(0<V≤100\)

核心思想:

这题本来是一个朴素的二维费用背包问题,但是这题有两个值得深究的点:

1.怎么去找最多能装几个,最多能剩多少

我们设\(f_{i,j}\)表示\(m\)\(i\)\(v\)\(j\)时所收集到的最大精灵。

然后我们就可以列出一个平凡的三重循环的DP

但是还没有完,我们要求的最多能装几个好说,怎么求能剩多少呢

这也好办,如果此时的\(f_{i,j}\)\(ans\)数组要大,那么\(tag\)直接标记成\(j\)

但是如果两者相等,就取两者较小的

最后直接输出即可

int f[N][N],u[N],v[N];
int n,m,q,tagans,tag;
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=q;i++) scanf("%d%d",&u[i],&v[i]);
    for(int i=1;i<=q;i++)
        for(int j=n;j>=u[i];j--)
            for(int k=m;k>=v[i];k--)
                f[j][k]=max(f[j][k],f[j-u[i]][k-v[i]]+1);
    for(int j=0;j<=n;j++){
        for(int k=0;k<m;k++){
            if(f[j][k]>tagans) tagans=f[j][k],tag=k;
            else if(f[j][k]==tagans && tag>k) tag=k;
    printf("%d %d",tagans,m-tag);
    return 0;
}

2.我们观察数据发现什么

第一种写法的时间复杂度我们容易发现是\(O(nmV)\)

我们首先可以理解一个问题就是对于一个背包来说,\(f_{i,j}\)表示枚举第\(i\)样物品的体积是\(j\)时的最大价值

那么此时的\(v[i]\)\(w[i]\)是可以互换的,只是会更改\(f\)数组的意义

那我们回来看这个题,由于\(V\)很小,我们考虑多次使用\(V\)

所以我们的新的\(f\)数组表示v为\(i\), 枚举到第\(j\)个物品用了最小的\(m\)

所以我们此时就可以换一种写法

const int inf=0x3f3f3f3f;
int f[N][N];
int n,m,q;
int main(){
    memset(f,0x3f,sizeof(f));
    scanf("%d%d%d",&n,&m,&q);
    f[0][0]=0;
    for(int i=1,u,v;i<=q;i++){
        scanf("%d%d",&u,&v);
        for(int j=m;j>=v;j--)
            for(int k=i;k;k--)
                if(f[j-v][k-1]+u<=n) f[j][k]=min(f[j][k],f[j-v][k-1]+u); 
    }
    for(int k=q;~k;k--)
        for(int j=0;j<m;j++)
            if(f[j][k]!=inf) return printf("%d %d\n",k,m-j),0; 
    return 0;
}

这时候我们分析复杂度就是\(O(V^2m)\)算出来大概是5e6,前面那个算出来是5e7,区别还是很大的

数字组合

题目描述:

给定\(n\)个正整数\(A1,A2,…,An\),从中选出若干个数,使它们的和为\(m\),求有多少种选择方案。

\(1≤n≤100\)
\(1≤m≤10000\)
\(1≤Ai≤1000\)

核心思想:

这个题是要求方案数,是一类很经典的DP了

首先,我们要设\(f_{j}\)表示和为\(j\)的方案数

那么对于\(j\)来说,假如说存在\(i∈[1,n]\)\(a[i]+k=j\)那么我们的\(f[j]=f[j]+f[k]\)就是说\(f_k\)的所有方案数都可以通过加上一个数凑成\(j\),所以我们可以得出\(f_j+=f_j-a[i]\)

因为每个数只能选一次,所以我们直接用01背包操作即可

注意:f[0]要赋成1

int f[N],a[N];
int n,m;
int main(){
    read(n),read(m);
    f[0]=1;
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=n;i++){
        for(int j=m;j>=a[i];j--){
            f[j]=f[j]+f[j-a[i]];
        }
    }
    cout<<f[m];
    return 0;
}

货币系统

题目描述:

给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。

核心思想:

这个题和上一个题差不多,但是我们发现我们的当前的加钱可以有若干张统一价钱的拼接起来,直接完全背包就行了啊

#define int long long
int f[N];
signed main(){
    int n,m;
    cin>>n>>m;
    f[0]=1;
    for(int i=1;i<=n;i++){
        int u; cin>>u;
        for(int j=u;j<=m;j++) f[j]+=f[j-u];
    }
    cout<<f[m];
    return 0;
}

混合背包问题

题目描述

\(N\)种物品和一个容量是 \(V\) 的背包。

物品一共有三类:

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

每种体积是\(vi\),价值是\(wi\)

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

核心思想:

我们考虑一个问题我们在二进制优化时我们是把每个物品拆分,那么此时我们可以直接套用二进制拆分的思想,01背包就转化成\(s=1\)的多重背包,完全背包转化为\(s=m/u\)的多重背包

最后用多重背包思路求解即可QAQ

#include <bits/stdc++.h>
using namespace std;
int n,m,v[100010],w[100010],f[100010];
int main(){
    cin>>n>>m;
    int cnt=1;
    for(int i=1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        if(s<0)s=1;
        else if(s==0)s=m/a;
//把01背包和多重背包先转化成多重背包,若为完全背包,则在最优情况下,只能取总体积/该物品体积向下取整 
        while(k<=s){
            v[cnt]=a*k;
            w[cnt]=b*k;
            s-=k;
            k*=2;
            cnt++;
        }
        if(s>0){
            v[cnt]=s*a;
            w[cnt]=s*b;
            cnt++;
        }
    }//将多重背包进行二进制优化,变成01背包 
    for(int i=1;i<=cnt;i++){
        for(int j=m;j>=v[i];j--)
            f[j]=max(f[j],f[j-v[i]]+w[i]); //01背包问题
    cout<<f[m]<<endl;
    return 0;
}

背包问题方案数

题目描述:

\(N\)件物品和一个容量是\(V\)的背包。每件物品只能使用一次。

\(i\)件物品的体积是\(vi\),价值是\(wi\)

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最优选法的方案数。注意答案可能很大,请输出答案模1e9+7的结果。

核心思想

每次更新答案时判断一下,记录一个\(cnt\)数组,初始为1,表示背包容积为\(i\)时总价值为最佳的方案数,那么当\(f\)被更新时,\(cnt_j\)就会被更新成\(cnt_{j-u}\),那么\(cnt_j\)加上\(cnt_{j-u}\)即可

const int N=1e5+10;
const int mod=1e9+7;
int f[N];
int a[N],u[N],v[N];
int cnt[N];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<=m;i++) cnt[i]=1;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&u[i],&v[i]);
        for(int j=m;j>=u[i];j--){
            if(f[j]<f[j-u[i]]+v[i]){
                f[j]=f[j-u[i]]+v[i];
                cnt[j]=cnt[j-u[i]];
            }
            else if(f[j]==f[j-u[i]]+v[i]) cnt[j]=(cnt[j]+cnt[j-u[i]])%mod;
        }
    }
    cout<<cnt[m];
}
posted @ 2021-11-01 00:00  RevolutionBP  阅读(81)  评论(0编辑  收藏  举报