【动态规划-背包模型part4(混合背包问题、背包问题方案数量)】:混合背包问题、机器分配、背包问题求方案数【已更新完成】

题目类型
1、混合背包问题混合背包问题
2、机器分配背包问题方案数量
3、背包问题求方案数背包问题方案数量

1、混合背包问题

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

第一类物品只能用1次(01背包); 第二类物品可以用无限次(完全背包); 第三类物品最多只能用 si 次(多重背包); 每种体积是 vi ,价值是 wi 。

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

输入格式
第一行两个整数,N,V ,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si ,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

si=−1 表示第 i 种物品只能用1次; si=0 表示第 i 种物品可以用无限次; si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。

数据范围

0<N,V≤1000

0<vi,wi≤1000

−1≤si≤1000

输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8

思路:

完全背包问题可以转化为多重背包问题,这是本题的关键所在,完全背包能用无数次,等价于可以一种物品用到背包满为止,所以我们有/转化公式:

s[i]=m/v[i];

将完全背包转化为多重背包

另外,本题采用二进制优化来加快程序运行速度,二进制优化打包后就相当于01背包问题

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=100010;

int v[N],w[N],s[N];
int chmodv[N];
int chmodw[N];
int f[N];

//将完全背包转化为多重背包(能取无数次相当于有背包体积/v个物品)
//-1 01 0 完全 >0 多重

int n,m;//m是背包容积
int main()
{
    cin>>n>>m;

    for(int i=1;i<=n;i++)
    {

        cin>>v[i]>>w[i]>>s[i];
        if(s[i]==-1)s[i]=1;//01背包转化为多重背包
        else if(s[i]==0)s[i]=m/v[i];//完全背包转化为多重背包

    }

    //二进制优化(朴素写法要三重循环,在第三重循环枚举s[i])
    //二进制优化后,多个物品被打包,相当于01背包问题
    //所以只需要01背包的做法(二重循环)
    int cnt=1;
    for(int i=1;i<=n;i++)
    {
        int k=1;//用来二进制打包
        while(k<=s[i])
        {
            chmodv[cnt]=v[i]*k;
            chmodw[cnt]=w[i]*k;
            s[i]-=k;
            k*=2;
            cnt++;
        }
        if(s[i]>0)
        {
            chmodv[cnt]=s[i]*v[i];
            chmodw[cnt]=s[i]*w[i];
            cnt++;
        }
    }///二进制优化完成

//    朴素写法
//    for(int i=1;i<=cnt;i++)//这时候是多个物品打包在一起,做法和01背包一样
//    {
//        for(int j=0;j<=m;j++)
//        {
//            f[i][j]=f[i-1][j];
//            if(j>=chmodv[i])f[i][j]=max(f[i][j],f[i-1][j-chmodv[i]]+chmodw[i]);
//        }
//
//    }

//  滚动数组写法(可以储存更多的状态,如果是二维存储的状态不够用)

    for(int i=1;i<=cnt;i++)
    {
        for(int j=m;j>=chmodv[i];j--)
        {
            f[j]=max(f[j],f[j-chmodv[i]]+chmodw[i]);
        }
    }


    cout<<f[m];

    return 0;
}

2、机器分配

总公司拥有 M 台 相同 的高效设备,准备分给下属的 N 个分公司。

各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。

问:如何分配这M台设备才能使国家得到的盈利最大?

求出最大盈利值。

分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M 。

输入格式
第一行有两个数,第一个数是分公司数 N ,第二个数是设备台数 M ;

接下来是一个 N×M 的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。

输出格式 第一行输出最大盈利值;

接下 N 行,每行有 2 个数,即分公司编号和该分公司获得设备台数。

答案不唯一,输出任意合法方案即可。

数据范围
1≤N≤10 , 1≤M≤15
输入样例:
3 3
30 40 50
20 30 50
20 25 30
输出样例:
70 1 1
2 1 3 1

思路:

每一个公司可以看作一个组,从每一组中选
最终用dfs寻找路径:!!从最终状态开始dfs!!倒推到初始状态

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=12,M=17;

int n,m;//公司数量和机器数量

int g[N][M];

int f[N][M];

int path[N];
int cnt;

void dfs(int i,int j)
{
    if(i==0)return ;
    for(int a=0;a<=j;a++)
    {

        if(f[i-1][j-a]+g[i][a]==f[i][j])
        {

            path[cnt++]=a;
            dfs(i-1,j-a);
            return ;
        }
    }

}

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

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&g[i][j]);//读入数据矩阵
        }

    //每个公司可以看作一个组,从每一组中选

    for(int i=1;i<=n;i++)//从前i组里面选
    {
        for(int j=1;j<=m;j++)//枚举体积(总共m台机器)
        {
            for(int k = 0;k <= j;k ++)
            {
                f[i][j]=max(f[i][j],f[i-1][j]);
                f[i][j]=max(f[i][j],f[i-1][j-k]+g[i][k]);
                //g[i][k]可以解读为第i组中体积为k,价值为g[i][k]
            }
        }
    }

    cout<<f[n][m]<<endl;

    //find path
    dfs(n,m);

    //因为是从最终状态开始find拓扑排序的
    //所以cnt==0的时候存储的是最终的状态
    //最终path要倒序输出
    for(int i=cnt-1,id=1;i>=0;id++,i--)
            cout<<id<<" "<<path[i]<<endl;
}

3、背包问题求方案数

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

第 i 件物品的体积是 vi ,价值是 wi 。

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

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

输入格式
第一行两个整数,N,V ,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi ,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示 方案数 模 109+7 的结果。

数据范围
0<N,V≤1000

0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2

思路:

cnt[i]存储价值为i的时候的方案数
相当于进行两次dp

代码:

#include<bits/stdc++.h>

using namespace std;

//01背包问题求方案数
//f[i]存储体积为i的时候的最高价值
//cnt[i]存储体积为i的时候的最高价值(f[i])对应的方案数

const int N=1003;

int f[N];
int cnt[N];

int v[N],w[N];

int mod=1e9+7;

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 0; i <= m; i ++)  cnt[i] = 1;//初始值全部设置为1
    //因为最高价值都可以为0,方案就可以有一种
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&v[i],&w[i]);
    }

    for(int i=1;i<=n;i++)
    for(int j=m;j>=v[i];j--)
        {
            int value=f[j-v[i]]+w[i];
            if(value>f[j])//找到价值更高的方案
            {
                f[j]=value;
                cnt[j]=cnt[j-v[i]];
            }
            else if(value==f[j])
            {
                cnt[j]=(cnt[j]+cnt[j-v[i]])%mod;
            }
        }
    cout<<cnt[m];
    return 0;
}


  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值