【Acwing提高】DP·背包

推荐:炒鸡棒的适合萌新的DP题单(大概?)

【Acwing提高】DP·背包

知识点

题目扩展方式扩展来源
采药裸的01
装箱问题价值=体积,最小转求最大01
宠物小精灵之收服价值为1,费用不为0,多关键字01二维费用
数字组合费用恰好,求方案数01
买书费用恰好,求方案数完全背包
货币系统1021求方案数,开longlong完全背包
货币系统531求方案数,模型转化,可行性完全背包
多重背包问题 III单调队列优化(滑动窗口)多重
庆功会裸的多重背包
混合背包问题大杂烩01,多重,完全
二维费用的背包问题二维费用01
潜水员费用变为至少,求min二维费用01
机器分配抽象转化,求具体方案分组背包
开心的金明裸的01
有依赖的背包问题树形依赖树形dp,分组,金明的预算方案
背包问题求方案数最优解方案数(最短路条数),体积恰好01
背包问题求具体方案物品逆向,字典序最小(贪心,求具体方案)01
能量石贪心01
金明的预算方案有依赖背包,两层依赖分组

题目

采药

思路
裸的01没啥好讲的,但是这里代码写的比自己的好,因为边输入边计算,节省空间了
代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,v,w;
const int N=1005;
ll f[N];

int main()
{
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>v>>w;//输入同时计算
        for(int j=m;j>=v;j--)f[j]=max(f[j],f[j-v]+w);
    }
    cout<<f[m];
    return 0;
}

装箱问题

思路
价值=体积,最小转求最大
代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,v;
const int N=2e4+10;
ll f[N];

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

宠物小精灵之收服

思路
捕获精灵数越多越好,如果相同,剩余体力越多越好

捕获精灵数越多越好
二维费用背包(具体推导看后面题目),价值为1,体力值不能为0是需要注意的点

同时,对于背包问题,体积和价值是可以互换的,因此根据数据范围选择体积和价值可以有效地降低时间复杂度
另外一种题解:(体力、精灵数为费用,精灵球数为价值) O ( K 2 M ) O(K^2M) O(K2M)

剩余体力越多越好找到最小的k使得 f [ V 1 ] [ k ] = = f [ V 1 ] [ V 2 − 1 ] f[V_1][k]==f[V_1][V_2-1] f[V1][k]==f[V1][V21]
代码
(体力、精灵球数为费用、精灵数为价值) O ( N M K ) O(NMK) O(NMK)

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=510;
int n,V1,V2;
int f[N][M];
int main()
{
    cin>>V1>>V2>>n;
    for(int i=1;i<=n;i++)
    {
        int v1,v2;
        cin>>v1>>v2;
        for(int j=V1;j>=v1;j--)
            for(int k=V2-1;k>=v2;k--)//体力值不能为0所以不能从V2开始
                f[j][k]=max(f[j][k],f[j-v1][k-v2]+1);
    }
    cout<<f[V1][V2-1]<<" ";
    int k=V2-1;
    while(k>0&&f[V1][k-1]==f[V1][V2-1])k--;//先判断后操作
    cout<<V2-k<<endl;
    return 0;
}

数字组合

思路
求方案数,并且恰好
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − v i ] f[i][j]=f[i-1][j]+f[i-1][j-v_i] f[i][j]=f[i1][j]+f[i1][jvi]不要写成
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − v i ] + 1 f[i][j]=f[i-1][j]+f[i-1][j-v_i]+1 f[i][j]=f[i1][j]+f[i1][jvi]+1
初始化的时候记得 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1其余为0
注意区分体积最多为j的初始化
在这里插入图片描述

代码

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

const int N=10010;

int n,m;
int f[N];
int main()
{
    cin>>n>>m;
    f[0]=1;//初始化为1,其他为0
    for(int i=0;i<n;i++)
    {
        int v;
        cin>>v;
        for(int j=m;j>=v;j--)
            f[j]+=f[j-v];
    }
    cout<<f[m];
    return 0;
}

买书

思路
费用恰好(要花完),完全背包求方案数,和数字组合很小
代码

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

const int N=1e3+10;
int f[N];
int a[N]={10,20,50,100};
int n;
int main()
{
    cin>>n;
    f[0]=1;//注意初始化
    for(int i=0;i<4;i++)
    {
        for(int j=a[i];j<=n;j++)
            f[j]+=f[j-a[i]];
    }
    cout<<f[n];
}

货币系统1021

思路
记得开long long
代码

#include<bits/stdc++.h>
using namespace std;
const int N=3e3+10;
typedef long long ll;
int n,m,k;
ll v,f[N];
int main()
{
    cin>>n>>m;
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
       cin>>v;
       for(int j=v;j<=m;j++)
            f[j]+=f[j-v];
    }
    cout<<f[m];
    return 0;
}

货币系统531

思路
根据题意,若两套货币系统相等,能表示的集合要相同,不能表示的集合也要相同。
可以得出:最优解一定从原序列中选出来,问题可以转化为某个面值是否必选。(最优性–>可行性问题–>可行性–>方案数)
那么,如何判断某一面值是否必选,可以转化为排序后,前1~i-1个面值凑成a[i]的方案数,若方案数为0,则a[i]必选,否则a[i]可以被前面的替换掉不必选。
模型转化为完全背包求方案数。
代码

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=25010;
int n;
int f[M],a[N];
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i=0;i<n;i++)cin>>a[i];
        sort(a,a+n);
        
        int m=a[n-1];
        memset(f,0,sizeof f);
        f[0]=1;
        
        int res=0;
        for(int i=0;i<n;i++)
        {
            if(!f[a[i]])res++;
            for(int j=a[i];j<=m;j++)
                f[j]+=f[j-a[i]];
        }
        cout<<res<<endl;
    }
    return 0;
}

多重背包问题 III

思路
比较好的题解
看数据模拟
滑动窗口图解
在这里插入图片描述
如何处理w的差值
在这里插入图片描述
摘自上面的博客里

所以,我们可以得到
dp[j]    =     dp[j]
dp[j+v]  = max(dp[j] +  w,  dp[j+v])
dp[j+2v] = max(dp[j] + 2w,  dp[j+v] +  w, dp[j+2v])
dp[j+3v] = max(dp[j] + 3w,  dp[j+v] + 2w, dp[j+2v] + w, dp[j+3v])
...
但是,这个队列中前面的数,每次都会增加一个 w ,所以我们需要做一些转换
dp[j]    =     dp[j]
dp[j+v]  = max(dp[j], dp[j+v] - w) + w
dp[j+2v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w) + 2w
dp[j+3v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w, dp[j+3v] - 3w) + 3w
...
这样,每次入队的值是 dp[j+k*v] - k*w
代码转化
放到下面代码里就是dp[k]-(k-j)/v*w

滑动窗口模板(以滑动窗口中min为例子)

 hh = 0; tt = -1;// 初始化
 for (int i = 0; i < n; ++ i)//遍历数轴
 {
     if (i - k + 1 > q[hh]) ++ hh;//如果第i项加进去超过窗口宽度,队首出队
     while (hh <= tt && a[i] <= a[q[tt]]) -- tt;//保持a[i]>a[q[tt]],单调递增
     q[++ tt] = i;
     if (i + 1 >= k) printf("%d ", a[q[hh]]);//队头即最小值
 }

代码

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

const int N=2e4+10;
int n,m;
int f[N],g[N],q[N];
//f存储的是第i层,g存储第i-1层,q存储的是f,g数组中的下标(体积,例如:q[5]=r+3v);
//g[k]=f[i-1][k]
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        memcpy(g,f,sizeof f);//复制上一层结果
        for(int j=0;j<v;j++)//枚举余数
        {
            int hh=0,tt=-1;
            for(int k=j;k<=m;k+=v)//枚举体积(即数轴坐标)
            {
                if(hh<=tt&&q[hh]<k-s*v)hh++;//看有没有超过s件(窗口长度太长)
                if(hh<=tt)f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w);
                //每次窗口max就是队头坐标转化后g[q[hh]]+(k-q[hh])/v*w
                while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w)tt--;
                //(k-q[hh])/v和(k-j)/v就是下标
                q[++tt]=k;
            }
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

庆功会

思路
裸的多重背包
代码

#include<bits/stdc++.h>
using namespace std;
const int N=6010;
int n,m;
int f[N];
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        for(int j=m;j>=0;j--)
            for(int k=0;k<=s&&k*v<=j;k++)//注意这个
                f[j]=max(f[j],f[j-k*v]+k*w);
    }
    cout<<f[m];
    return 0;
}

混合背包问题

思路
只要看第i个物品时啥类型背包就行了
代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int f[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=0;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;//01合并到多重里
            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]<<endl;
    
    return 0;
}

二维费用的背包问题

思路
在这里插入图片描述
代码

#include<bits/stdc++.h>
using namespace std;
int n,V1,V2,v1,v2,w;
const int N=1e3+10;
int f[N][N];
int main()
{
    cin>>n>>V1>>V2;
    for(int i=1;i<=n;i++)
    {
        cin>>v1>>v2>>w;
        for(int j=V1;j>=v1;j--)
            for(int k=V2;k>=v2;k--)
                f[j][k]=max(f[j][k],f[j-v1][k-v2]+w);
    }
    cout<<f[V1][V2];
    return 0;
}

潜水员

思路
推荐:背包初始化
在这里插入图片描述

代码

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

const int N=22,M=80;

int n,m,k;
int f[N][M];

int main()
{
    cin>>n>>m>>k;
    memset(f,0x3f,sizeof f);//初始化为INF,只有合法地转移,
    f[0][0]=0;
    while(k--)
    {
        int v1,v2,w;
        cin>>v1>>v2>>w;
        for(int j=n;j>=0;j--)//>=0,负数是合法的,因为至少
            for(int k=m;k>=0;k--)
                f[j][k]=min(f[j][k],f[max(0,j-v1)][max(0,k-v2)]+w);
       /*
       for(int j=n;j>=v1;j--)//这样不能让负数也转移
            for(int k=m;k>=v2;k--)
                f[j][k]=min(f[j][k],f[j-v1][k-v2]+w);//max(0,j-v1)不是说到0就可以了,只是因为负数至少等效于取0,仍旧要转移的
       */
               
    }
    cout<<f[n][m]<<endl;
    return 0;
}

机器分配

思路
把公司当作物品组,机器数当作体积,价值为所给矩阵,转化为分组背包问题(同一个物品组只能选一个物品或者不选)
在这里插入图片描述

在这里插入图片描述

代码

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

int w[20][20],f[20][20],way[20];
int n,m;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>w[i][j];
    
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];//第i组一个都不选
            for(int k=1;k<=j;k++)
                f[i][j]=max(f[i][j],f[i-1][j-k]+w[i][k]);
        }
    cout<<f[n][m]<<endl;
    int j=m;
    for(int i=n;i;i--)
        for(int k=0;k<=j;k++)
            if(f[i][j]==f[i-1][j-k]+w[i][k])
            {
                way[i]=k;
                j-=k;
                break;//找到直接跳出就行了
            }
    for(int i=1;i<=n;i++)cout<<i<<" "<<way[i]<<endl;
    return 0;
}

开心的金明

思路
裸的01
代码

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+10;
int n,m,k;
int v,w,dp[N];
int main()
{
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>v>>w;
        w*=v;
        for(int j=m;j>=v;j--)
            dp[j]=max(dp[j],dp[j-v]+w);
    }
    cout<<dp[m];
    return 0;
}

有依赖的背包问题

思路
树形DP,把每个子树作为物品组,以体积来划分

啊这里对于树中的每个节点来说,就是一个分组背包问题。每个子节点是一组物品,
每个子节点的不同体积和每个体积所对应的最大价值,就是这个物品组中的物品。

图解版的题解
在这里插入图片描述
在这里插入图片描述
只是把分组背包的组换成根节点,物品换成子树。区别是第三重循环决策不再按选哪个物品(时间复杂度太高)而是分配个每个子树的体积
在这里插入图片描述

代码

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

const int N=110;
int n,m;
int h[N],e[N],ne[N],idx;
int v[N],w[N];
int f[N][N];

void add(int a,int b)//邻接表
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u)
{
    for(int i=h[u];~i;i=ne[i])//循环物品组
    {
        int son=e[i];
        dfs(e[i]);
        
        //分组背包
        //这个时候当前结点我们看成是分组背包中的一个组,子节点的每一种选择我们都看作是组内一种物品
        for(int j=m-v[u];j>=0;j--)//循环体积,注意m-v[u]默认选根节点
            for(int k=0;k<=j;k++)//循环决策,给子节点son分配多少体积
                f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
    }
    //把物品u加进去
    for(int i=m;i>=v[u];i--)f[u][i]=f[u][i-v[u]]+w[u];//别忘记默认选根节点
    for(int i=0;i<v[u];i++)f[u][i]=0;//如果根节点都装不下
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);//初始化
    int root;
    for(int i=1;i<=n;i++)
    {
        int p;
        cin>>v[i]>>w[i]>>p;
        if(p==-1)root=i;//根
        else add(p,i);
    }
    dfs(root);
    
    cout<<f[root][m]<<endl;
    return 0;
}

背包问题求方案数

思路
可以想成求最短路条数
法一:
定义 f [ i ] [ j ] f[i][j] f[i][j]为从前i个物品中选,体积恰好为j的选法集合
f [ i ] [ j ] = m a x ( f [ i − 1 ] j ] , f [ i − 1 ] [ j − v ] + w ) f[i][j]=max(f[i-1]j],f[i-1][j-v]+w) f[i][j]=max(f[i1]j],f[i1][jv]+w)

开一个 g [ i ] [ j ] g[i][j] g[i][j] f [ i ] [ j ] f[i][j] f[i][j]取到最优解方案数
不选第i个大 g [ i ] [ j ] = g [ i − 1 ] [ j ] g[i][j]=g[i-1][j] g[i][j]=g[i1][j]
选第i个大 g [ i ] [ j ] = g [ i − 1 ] [ j − v ] g[i][j]=g[i-1][j-v] g[i][j]=g[i1][jv]
选不选第i个一样大 g [ i ] [ j ] = g [ i − 1 ] [ j ] + g [ i − 1 ] [ j − v ] g[i][j]=g[i-1][j]+g[i-1][j-v] g[i][j]=g[i1][j]+g[i1][jv]
因为体积是恰好,所以要遍历一遍,求最大值(f[m]不是最大值)
然后再遍历一遍看看有没有相等的再求和
注意初始化

法二:
滑稽大佬的题解

关注两种方法的初始化问题

代码
法一

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
const int N=1e3+10,mod=1e9+7;
ll f[N],g[N];
int main()
{
    cin>>n>>m;
    memset(f,-0x3f,sizeof f);//不能使体积恰好为j的不能被递推
    f[0]=0,g[0]=1;//显然选体积为0价值为0,而什么都不选的选法为1
    for(int i=0;i<n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--)
        {
            if(f[j]<f[j-v]+w)
            {
                f[j]=f[j-v]+w;
                g[j]=g[j-v]%mod;
            }
            else if(f[j]==f[j-v]+w)
            {
                g[j]=(g[j]+g[j-v])%mod;
            }
        }
    }
    ll res=0,cnt=0;
    for(int i=0;i<=m;i++)res=max(res,f[i]);
    for(int i=0;i<=m;i++)
        if(res==f[i])cnt=(cnt+g[i])%mod;
    cout<<cnt;
    return 0;
}

法二代码(滑稽大佬的)

#include<iostream>
#include<algorithm>

using namespace std;

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

int f[N],g[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<=m;i++) g[i]=1;//初始化时我们易知,不论是哪个体积下,总有一个对应的最大价值,方案数为1

    for(int i=1;i<=n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--)
        {
            if(f[j]<f[j-v]+w)
            {
                g[j]=g[j-v]; //当f[j]<f[j-v]+w时,说明g[j]只能从上层转移过来了
                f[j]=f[j-v]+w;
            }
            else if(f[j]==f[j-v]+w) g[j]=(g[j]+g[j-v])%mod;//若相等,说明存在了2个节点,他们路径都符合条件
            //可以递推到g[j]
        }
    }

    cout<< g[m] <<endl;//最后输出这个体积不超过m对应最大价值的方案数即可!
    return 0;
}

背包问题求具体方案

思路
推荐题解参考
求获得最大价值的具体方案,并令字典序最小
求具体方案:判断出每个每个物品是否被选
首先不能进行状态压缩
记录方案,从哪个路径走到 f [ n ] [ m ] f[n][m] f[n][m]
怎么判断?
如果 f [ n ] [ m ] = f [ n − 1 ] [ m ] 那 么 从 不 选 第 n 个 转 移 过 来 如 果 f[n][m]=f[n-1][m]那么从不选第n个转移过来 如果 f[n][m]=f[n1][m]nf[n][m]=f[n-1][m-v[n]]+w[n]$那么从选第n个转移过来
也可能两个都可以,即第n个物品可选可不选

字典序最小:贪心
从第一个开始,每个物品有选有三种情况
只能选–>一定选
只能不选–>一定不选
可选可不选–>一定选

但是我们一般dp的时候是倒着推具体方案的,我们要最小字典序是要从前往后推如何解决呢?那么在输入之后,dp的时候从后往前推即可。

另外一种思路是开一个数组记录选哪个
代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N][N],v[N],w[N];
int n,m;
int main()
{   
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
    for(int i=n;i>=1;i--)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i+1][j];//从后往前别写错
            if(j>=v[i])f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
        }
    }
    //f[1][m]是最大值
    int j=m;
    for(int i=1;i<=n;i++)
    {
        if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])//能选就一定要选   
        {
            cout<<i<<" ";
            j-=v[i];
        }
    }
    return 0;
}

能量石

思路
讲的比较好的博客
暴力解法:把把所有全排列整出来然后每个排列做01背包取最值
这里有个问题:为啥不能直接01背包?
对于一般的选物品,无论物品如何排列,我们都可以得到相同的答案。但是本题的特殊点在于,不同顺序选的话,物品的价值是变的,前面选的物品时间越长,后面物品价值越小(甚至为0),这样DP具有后效性,没法整,出来的只是局部最优解。
这时候我们可以利用贪心缩小决策范围,将最优解的排序确定,然后进行01
贪心:
其中 S i S_i Si表示前i个时间之和(前缀)
S i = t 1 + t 2 + … … + t i S_i=t_1+t_2+……+t_i Si=t1+t2++ti
E 1 − L 1 + E 2 − L 2 ∗ S 1 + … … + E n − L n ∗ S n − 1 E_1-L_1+E_2-L_2*S_1+……+E_n-L_n*S_{n-1} E1L1+E2L2S1++EnLnSn1
第i项和第i+1邻项交换

状态公式
交换前 E i − L i ∗ S i − 1 + E i + 1 − L i + 1 ∗ S i E_i-L_i*S_{i-1}+E_{i+1}-L_{i+1}*S_i EiLiSi1+Ei+1Li+1Si
交换后 E i + 1 − L i + 1 ∗ S i − 1 + E i − L i ∗ ( S i − t i + t i + 1 ) E_{i+1}-L_{i+1}*S_{i-1}+E_{i}-L_{i}*(S_i-t_i+t_{i+1}) Ei+1Li+1Si1+EiLi(Siti+ti+1)
比较 L i t i ? L i + 1 t i + 1 \frac {L_i} {t_i}?\frac {L_{i+1}} {t_{i+1}} tiLi?ti+1Li+1

排序后产生另外一个问题:为啥不直接拿完最优解排序。
因为你拿一块能量石后未必会增加总能量,反而可能因为拿了这块后造成后面的能量损失过多,因此如果选择不拿的话是有可能减少这些损失的,反而有利。
DP状态转移方程
定义 f [ i ] [ j ] f[i][j] f[i][j]为从前i个物品中选,耗时恰好为j的所有选法集合的最大价值
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − s [ i ] ] + m a x ( 0 , e [ i ] − l [ i ] ∗ ( j − s ) ) ) f[i][j]=max(f[i-1][j],f[i-1][j-s[i]]+max(0,e[i]-l[i]*(j-s))) f[i][j]=max(f[i1][j],f[i1][js[i]]+max(0,e[i]l[i](js)))
代码

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

const int N=1e4+10;
int n;
struct Stone
{
    int s,e,l;
    bool operator<(const Stone &W)const
    {
        return s*W.l<l*W.s;
    }
}stone[N];
int f[N];
int main()
{
    int T;
    cin>>T;
    for(int C=1;C<=T;C++)
    {
        int m=0;
        cin>>n;
        for(int i=0;i<n;i++)
        {
            int s,e,l;
            cin>>s>>e>>l;
            stone[i]={s,e,l};
            m+=s;
        }
        sort(stone,stone+n);
        memset(f,-0x3f,sizeof f);
        f[0]=0;
        for(int i=0;i<n;i++)
        {
            int s=stone[i].s,e=stone[i].e,l=stone[i].l;
            for(int j=m;j>=s;j--)
                f[j]=max(f[j],f[j-s]+e-(j-s)*l);
        }
        int res=0;
        for(int i=0;i<=m;i++)res=max(res,f[i]);
        printf("Case #%d: %d\n",C,res);
    }
    return 0;
}

金明的预算方案

思路
有依赖的分组背包问题
模型的转化
分组背包:每个主件的附件决策选法其实就是一个物品,他们是互斥的
在这里插入图片描述

关系的输入与存储
在这里插入图片描述
如何用代码实现上述这种一个连着多个而且只有一层关系的结构(附件不会成为附件的附件)
用vector数组即可,同时它仅有两种属性,那么可以用pair类型的vector存储这种依赖关系,如果是根,则存PII类型的master数组中,如果不是则存servent的vector数组。如果有多个属性的话把PII改成struct存储即可

PII master[N];
vector<PII>servent[N];

如何枚举每个选法:通过观察发现我们可以利用二进制来实现枚举例如第一张图的主2
默认初值选上主件,每个附件选或不选,二进制枚举附件选法
代码

#include<bits/stdc++.h>
using namespace std;
#define v first
#define w second

typedef long long ll;
const int N=4e4+10;
typedef pair<int,int>PII;
ll n,m;
PII master[N];
vector<PII>servent[N];//附件
int f[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<=m;i++)
        if(master[i].v)
        {
            for(int j=m;j>=0;j--)
            {
                auto &sv=servent[i];
                for(int k=0;k<(1<<sv.size());k++)//二进制枚举附件选法
                {
                    int v=master[i].v,w=master[i].w;//默认选主件
                    for(int u=0;u<sv.size();u++)
                        if(k>>u&1)
                        {
                            v+=sv[u].v;
                            w+=sv[u].w;
                        }
                    if(j>=v)f[j]=max(f[j],f[j-v]+w);
                }
            }
        }
    cout<<f[m]<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值