动态规划(2.2)背包问题扩展

目录

一、对体积的限制

(1)体积不大于

(2)体积恰好等于

(3)体积至少为

二、分组背包问题拓展

(1)Acwing 487

(2)背包问题结合树、dfs(有依赖的背包问题)

三、混合背包问题

四、背包问题求最优方案数

五、背包问题结合贪心


一、对体积的限制

参考文章icon-default.png?t=M7J4https://www.acwing.com/file_system/file/content/whole/index/content/1306630/

求方案数初始化 

二维情况:

  • 体积至多j,f(0,i)=1,i=0~m,其余是0。
  • 体积恰好j,f(0,0)=1,其余是0。
  • 体积至少j,f(0,0)=1,其余是0。

一维情况:

  • 体积至多j,f(i)=1,i=0~m。
  • 体积恰好j,f(0)=1,其余是0.
  • 体积至少j,f(0)=1,其余是0

求最大最小值初始化总结

二维情况:

  • 体积至多j,f(i,k)=0,i=0~n,k=0~m。(只能求最大值)
  • 体积恰好j,
    • 求价值最大值:f(0,0)=0,其余-INF
    • 求价值最小值:f(0,0)=0,其余INF(只会从0,0与物品的体积和价值,特定的更新某些值)
  •   体积至少j,f(0,0)=0,其余INF。(只能求最小值)

一维情况:

  • 体积至多j,f全为0
  • 体积恰好j,
    • 求价值最大值,f(0)=0,其余-INF
    • 求价值最小值,f(0)=0,其余INF
  • 体积至少j,f(0)=0,其余INF

下面展示部分求价值最大值时,体积恰好j,体积至少j的代码,注意观察循环体内写法区别和意,求体积至多j的问题不再赘述。

01背包体积恰好j

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;

    memset(f, -INF, sizeof f);
    f[0] = 0;

    for(int i = 1;i <= n;i ++)
    {
        int v, w;
        cin >> v >> w;
        for(int j = m;j >= v;j --)
        {
            f[j] = max(f[j], f[j - v] + w);
        }
    }

    cout << f[m] << endl;

    return 0;
}

作者:小呆呆
链接:https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

完全背包恰好j

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;

    memset(f, -INF, sizeof f);
    f[0] = 0;

    for(int i = 1;i <= n;i ++)
    {
        int v, w;
        cin >> v >> w;
        for(int j = v;j <= m;j ++)
        {
            f[j] = max(f[j], f[j - v] + w);
        }
    }

    cout << f[m] << endl;

    return 0;
}

作者:小呆呆
链接:https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

01背包至少j(只能求价值最小值)

不同点:恰好是j问题时,控制了j-v>=0,因为小于0的状态不存在,不可能由他们更新其它状态。而至少是j问题时,j-v<0的状态是合法的,等价于j-v==0,因此j层循环要遍历0~m,第二维状态写法为max(0,j-v)。

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;

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

    for(int i = 1;i <= n;i ++)
    {
        int v, w;
        cin >> v >> w;
        for(int j = m;j >= 0;j --)
        {
            f[j] = min(f[j], f[max(0, j - v)] + w);//即使物品体积比j大,j - v < 0,也能选,等价于f[0]
        }
    }

    cout << f[m] << endl;

    return 0;
}

作者:小呆呆
链接:https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 完全背包至少j(只能求价值最小值)

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;

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

    for(int i = 1;i <= n;i ++)
    {
        int v, w;
        cin >> v >> w;
        for(int j = 0;j <= m;j ++)
        {
            f[j] = min(f[j], f[max(0, j - v)] + w);//即使物品体积比j大,j - v < 0,也能选,等价于f[0]
        }
    }

    cout << f[m] << endl;

    return 0;
}

二、分组背包问题拓展

(1)Acwing 487

 题目要求:要选择附件就必需选择主件。因此我们可以把每一个主件看成一个组,将不选择、选择1个附件、两个附件、、k个附件打包看做背包中的物品,则问题转化为分组背包问题。每组分为不选择,选择主和Cn0个附+Cn1个附+....共2^n种情况。

表示:可以用二进制数来表示打包的方案。例如:1010表示选择了第2个、第4个附件和主件。

#include<iostream>
#include<algorithm>
#include<cstring>

//每组分为不选择,选择主和Cn0个附+Cn1个附+....共2^n种情况,转化为分组背包问题
using namespace std;
const int N =70,M=32010;

typedef pair<int,int> PII;

PII master[N];
int n,m;
int f[M];
vector<PII> servent[N];

int main()
{
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        int v,w,q;
        cin>>v>>w>>q;
        if(!q) master[i]={v,v*w};
        else servent[q].push_back({v,v*w});
    }

    for(int i=1;i<=n;i++)
    {
        if(master[i].first)
        {
            for(int j=m;j>=0;j--)
            {
                auto& sv=servent[i];
                for(int k=0;k<1<<sv.size();k++)
                {
                    int v=master[i].first,w=master[i].second;
                    for(int u=0;u<sv.size();u++)
                        if(k>>u&1)
                        {
                            v+=sv[u].first;
                            w+=sv[u].second;
                        }
                    if(j>=v) f[j]=max(f[j],f[j-v]+w);
                }

            }
        }
    }
    cout<<f[m];

}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4118462/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(2)背包问题结合树、dfs(有依赖的背包问题)

三、混合背包问题

 综合一下之前学过的,发现状态转移方程只和第i层的物品有关,和之前的物品种类无关。因此状态更新时用对应的转移方程即可。(这里用了一下代码简化版的2进制拆分解多重背包问题)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =1010;

int f[N];
int n,m;
//每种物品根据每种问题的状态转移方程做就可以,状态转移方程是独立的,不会因为其他物品的种类变化
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        if(s==0)
        {
            for(int j=v;j<=m;j++)
                f[j]=max(f[j],f[j-v]+w);
        }
        else
        {
            if(s==-1) s=1;
            for(int k=1;k<=s;k*=2)
            {
                for(int j=m;j>=k*v;j--)
                    f[j]=max(f[j],f[j-k*v]+k*w);
                s-=k;
            }
            if(s)
            {
                for(int j=m;j>=s*v;j--)
                    f[j]=max(f[j],f[j-s*v]+s*w);
            }
        }
    }
    cout<<f[m];
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4118828/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

四、背包问题求最优方案数

状态表示为体积恰好是j时,f表示最优方案的取值,g表示最优方案的数量。

f的状态转移方程不再赘述,g的状态转移方程分为两部分,不选第i件物品与选择第i件物品。如果不选是最优解,则g[j]=g[j],如果选第i件物品是最优解,则g[j]=g[j-v],如果两者相等,则g[j]=g[j]+g[j-v]。

最后遍历f求得全局最优解的值,然后遍历g,求得全局最优解方案的数量

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N =1010,mod=1e9+7;

int f[N],g[N];
int n,m;

int main()
{
    cin>>n>>m;
    //状态表示为体积恰好是j
    memset(f,0xcf,sizeof f);
    f[0]=0;
    g[0]=1;
    for(int i=1;i<=n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--)
        {
            int cnt=0;
            int maxv=max(f[j],f[j-v]+w);
            if(maxv==f[j])     cnt+=g[j];
            if(maxv==f[j-v]+w) cnt+=g[j-v];
            g[j]=cnt%mod;
            f[j]=maxv;
        }
    }

    int res=0;
    //由于状态表示是恰好,所以要遍历求出最优方案所得的值,然后再求最优方案出现的次数
    for(int i=0;i<=m;i++)  res=max(res,f[i]);

    int cnt=0;
    for(int i=0;i<=m;i++)
        if(res==f[i])
            cnt=(cnt+g[i])%mod;

    cout<<cnt;
    return 0;
}


作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4119420/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

五、背包问题结合贪心

 

直觉发现,如果前面吃能量石用过多时间,后面能量就会流失严重,因此选择时应该遵循某种顺序选择。

接下来假设有i,j两块能量石,探讨一下根据什么顺序选择。

如果按i,j顺序吃:E=e_i+e_j-s_i*l_{j}

如果按j,i顺序吃:E=e_i+e_j-s_j*l_{i}

因此选择顺序由s/l决定,s/l越小,后面能量流失就越小。

按s/l排序后,转化为01背包问题

为了计算损失的能量,状态表示为体积恰好等于j,状态转移方程为:

f[j]=max(f[j],f[j-s]+e-(j-s)*l)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N =1010,mod=1e9+7;

int f[N],g[N];
int n,m;

int main()
{
    cin>>n>>m;
    //状态表示为体积恰好是j
    memset(f,0xcf,sizeof f);
    f[0]=0;
    g[0]=1;
    for(int i=1;i<=n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--)
        {
            int cnt=0;
            int maxv=max(f[j],f[j-v]+w);
            if(maxv==f[j])     cnt+=g[j];
            if(maxv==f[j-v]+w) cnt+=g[j-v];
            g[j]=cnt%mod;
            f[j]=maxv;
        }
    }

    int res=0;
    //由于状态表示是恰好,所以要遍历求出最优方案所得的值,然后再求最优方案出现的次数
    for(int i=0;i<=m;i++)  res=max(res,f[i]);

    int cnt=0;
    for(int i=0;i<=m;i++)
        if(res==f[i])
            cnt=(cnt+g[i])%mod;

    cout<<cnt;
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4119420/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值