文章目录
推荐博客
背包九讲原文
背包九讲——全篇详细理解与代码实现
本博客对基础内容不进行解释,基础薄弱的可以与上述博客一起食用。
背包
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
int n,m;
int v[maxn],w[maxn],dp[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d%d",&w[i],&v[i]);//体积 价值
for(int i=0;i<n;i++)
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[m]);
return 0;
}
完全背包
可以不写这个优化,但是思想要理解,下面也会用到。
例题
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
int n,m;
int v[maxn],w[maxn],dp[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d%d",&w[i],&v[i]);//体积 价值
for(int i=0;i<n;i++)
for(int j=w[i];j<=m;j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[m]);
return 0;
}
多重背包
朴素版
例题
太慢了,基本不会用这种方法。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=105;
int n,m;
int v[maxn*maxn],w[maxn*maxn],dp[maxn];
int main()
{
scanf("%d%d",&n,&m);
int len=0,s;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&w[len],&v[len],&s);//体积 价值 数量
++len,--s;
while(s--)
w[len]=w[len-1],v[len]=v[len-1],++len;
}
for(int i=0;i<len;i++)
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[m]);
return 0;
}
二进制优化
基本思想:把
件物品用二进制形式表示出来,即
。且
满足:
。
正确性证明:首先
可以表示
的任意数,两端都加上
后可以表示
的任意数,由于
,所以
,即
,所以这些数可以表示的范围为
。
这个应该是最常用的优化了,复杂度为
。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=2005;
int n,m;
int v[10*maxn],w[10*maxn],dp[maxn];
int main()
{
scanf("%d%d",&n,&m);
int len=0,a,b,c;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);//体积 价值 数量
int tmp=1;
while(tmp<=c)
{
c-=tmp;
w[len]=tmp*a;
v[len++]=tmp*b;
tmp<<=1;
}
w[len]=c*a;
v[len++]=c*b;
}
for(int i=0;i<len;i++)
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[m]);
return 0;
}
单调队列优化
证明:这个写的挺好的,倒数第
行有一点点笔误。
倒数第
行,应该是
。
倒数第
行开始的
应该修改为
。
确实比较难理解,不过优化很给力,复杂度为
。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=2e4+5;
struct node //单调队列要用到
{
int idx,val;
node(){}
node(int i,int v):idx(i),val(v){}
}q[maxn];
int n,m;
int dp[maxn];
inline void cal(int v,int w,int c)//体积 价值 数量
{
int fon,bak,tmp;
for(int i=0;i<v;i++)
{
fon=bak=0;//fon=bak 时队列为空 fon<bak 时队列非空
for(int j=i,k=0;j<=m;j+=v,++k)
{
tmp=dp[j]-k*w;
if(fon<bak&&k-q[fon].idx>c)
++fon;
while(fon<bak&&tmp>=q[bak-1].val)
--bak;
q[bak++]=node(k,tmp);
dp[j]=q[fon].val+k*w;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
int a,b,c;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);//体积 价值 数量
cal(a,b,c);
}
printf("%d\n",dp[m]);
return 0;
}
混合背包
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
int n,m;
int w[maxn],v[maxn],dp[maxn];
int main()
{
scanf("%d%d",&n,&m);
int s,len;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&w[i],&v[i],&s);//体积 价值 数量
if(s==-1) //01背包
{
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
else if(s==0) //完全背包
{
for(int j=w[i];j<=m;j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
else //多重背包
{
int a=w[i],b=v[i],tmp=1;
len=0;
while(tmp<=s)
{
s-=tmp;
w[len]=a*tmp;
v[len++]=b*tmp;
tmp<<=1;
}
w[len]=a*s;
v[len++]=b*s;
for(int j=0;j<len;j++)
for(int k=m;k>=w[j];k--)
dp[k]=max(dp[k],dp[k-w[j]]+v[j]);
}
}
printf("%d\n",dp[m]);
return 0;
}
二维费用背包问题
解决方法同一维背包,只不过数组多了一维。
例题
扫描二维码关注公众号,回复:
9654735 查看本文章
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
int n,V,M;
int v[maxn],m[maxn],w[maxn],dp[maxn][maxn];
int main()
{
scanf("%d%d%d",&n,&V,&M);//数量 背包容积 背包可承重量
for(int i=0;i<n;i++)
scanf("%d%d%d",&v[i],&m[i],&w[i]);//体积 重量 价值
for(int i=0;i<n;i++) //01 二维背包
for(int j=V;j>=v[i];j--)
for(int k=M;k>=m[i];k--)
dp[j][k]=max(dp[j][k],dp[j-v[i]][k-m[i]]+w[i]);
printf("%d\n",dp[V][M]);
return 0;
}
分组背包
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=105;
int n,m;
int w[maxn][maxn],v[maxn][maxn],dp[maxn],s[maxn];
int main()
{
scanf("%d%d",&n,&m);//物品组数 背包容量
for(int i=0;i<n;i++)
{
scanf("%d",&s[i]);//第i组物品的物品数量
for(int j=0;j<s[i];j++)
scanf("%d%d",&w[i][j],&v[i][j]);//体积 价值
}
for(int i=0;i<n;i++)//第i组
for(int j=m;j>=0;j--)//注意循环顺序 保证该组只选1个
for(int k=0;k<s[i];k++)
if(j>=w[i][k])
dp[j]=max(dp[j],dp[j-w[i][k]]+v[i][k]);
printf("%d\n",dp[m]);
return 0;
}
有依赖背包问题
上图算法这一块内容非常关键,建议多读几遍好好体会。
例题
复杂度
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=105;
struct Edge
{
int to,nxt;
}edge[maxn<<1];
int n,m,tot;
int head[maxn];
int val[maxn],w[maxn],dp[maxn][maxn];
//dp[i][j]表示在以i为根的子树中 背包容量为j时的最优解
inline void addedge(int u,int v)
{
edge[++tot].to=v,edge[tot].nxt=head[u],head[u]=tot;
}
void dfs(int u,int fa)
{
int v;
for(int i=head[u];i;i=edge[i].nxt)
{
v=edge[i].to;
if(v==fa)
continue;
dfs(v,u);//递归处理子节点
for(int j=m-w[u];j>=w[v];j--)//分组背包 由于u必选 所以j的上限为m-w[u]
for(int k=j;k>=w[v];k--) //由于v必选 所以k的下限为w[v]
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
for(int j=m;j>=w[u];j--) //有依赖关系 所以必须选上节点u
dp[u][j]=dp[u][j-w[u]]+val[u];
}
int main()
{
scanf("%d%d",&n,&m);
int s,root;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&w[i],&val[i],&s);//体积 价值 依赖的物品编号
if(s==-1)
root=i;
else
addedge(s,i),addedge(i,s);
}
dfs(root,root);
printf("%d\n",dp[root][m]);
return 0;
}
泛化物品
背包问题问法的变化
输出字典序最小的最优方案
感觉上面有一处笔误,倒数第二行应该是
。
例题
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
int n,m;
int v[maxn],w[maxn],dp[maxn][maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=n;i>=1;i--) //逆序读入
scanf("%d%d",&w[i],&v[i]); //体积 价值
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
for(int j=w[i]-1;j>=0;j--)
dp[i][j]=dp[i-1][j];
}
int val=m;
for(int i=n;i>=1;i--)
{
if(val>=w[i]&&dp[i][val]==dp[i-1][val-w[i]]+v[i])
{
val-=w[i];
printf("%d ",n-i+1);
}
}
return 0;
}
输出最优解的方案数
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
const int mod=1e9+7;
int n,m;
int v[maxn],w[maxn],dp[maxn],num[maxn];
//dp[i]表示恰好装满体积为i的背包时所能得到的最大价值
//num[i]表示背包内物品体积之和为i时的方案数
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) //逆序读入
scanf("%d%d",&w[i],&v[i]); //体积 价值
memset(dp,-INF,sizeof(dp));
dp[0]=0,num[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)
{
if(dp[j]<dp[j-w[i]]+v[i])
{
dp[j]=dp[j-w[i]]+v[i];
num[j]=num[j-w[i]];
}
else if(dp[j]==dp[j-w[i]]+v[i])
num[j]=(num[j]+num[j-w[i]])%mod;
}
}
int ans=0,MAX=0;
for(int i=1;i<=m;i++)
MAX=max(MAX,dp[i]);
for(int i=1;i<=m;i++)
if(dp[i]==MAX)
ans=(ans+num[i])%mod;
printf("%d\n",ans);
return 0;
}
第 优解
int kth(int n, int V, int k) {
for (int i = 1; i <= n; i++) {
for (int j = V; j >= w[i]; j--) {
for (int l = 1; l <= k; l++) {
a[l] = f[j][l];
b[l] = f[j - w[i]][l] + v[i];
}
a[k + 1] = -1;
b[k + 1] = -1;
int x = 1, y = 1, o = 1;
while (o != k + 1 and (a[x] != -1 or b[y] != -1)) {
if (a[x] > b[y]) f[j][o] = a[x], x++;
else f[j][o] = b[y], y++;
if (f[j][o] != f[j][o - 1]) o++;
}
}
}
return f[V][k];
}
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1005;
int n,m,k;
int v[maxn],w[maxn],dp[maxn][35];
int a[maxn],b[maxn];
void kth(int n,int V,int k)
{
for(int i=1;i<=n;i++)
{
for(int j=V;j>=w[i];j--)
{
for(int l=1;l<=k;l++)
a[l]=dp[j][l],b[l]=dp[j-w[i]][l]+v[i];
a[k+1]=-1,b[k+1]=-1;
int x=1,y=1,id=1;
while(id<=k&&(a[x]!=-1||b[y]!=-1))
{
if(a[x]>b[y])
dp[j][id]=a[x],++x;
else
dp[j][id]=b[y],++y;
if(dp[j][id]!=dp[j][id-1])
++id;
}
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(dp,0,sizeof(dp));
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
scanf("%d",&v[i]); //价值
for(int i=1;i<=n;i++)
scanf("%d",&w[i]); //体积
kth(n,m,k);
printf("%d\n",dp[m][k]);
}
return 0;
}