由于笔记尚未完工,内容散碎而无体系,有价值的内容在于题目的推荐,之后会逐渐完善
写在前面
背包问题是我所了解的dp问题中最简单的一类,因为实在是有太多的套路已经被世人知晓,背包九讲中除了一些背包的变型(也称为变型背包)就都全部涉及了,所以今天打算把0-1,完全,多重,分组,依赖(以树上背包为例),变型,优化等背包问题做一次刷题和总结。其中优化和变型会穿插在前面的背包类型中。
本文章是笔者自己的笔记总结,不会涉及基础知识,全片内容以题目和思想为主,不是面向新手和高手的文章,面向学习完背包基础内容后想要进一步提高背包思想的同学,如果您还未学会背包问题(指尚不能稳定独立解决基础的各种背包问题)请先学习站内背包九讲。
0-1背包
作为背包问题的始祖,0-1背包是最为根本的,之后的所有背包都是依靠转化为01背包来解决的。
- 恰好装满0-1背包问题
背包的恰好装满问题通常也会和各种变型结合在一起,比如:数字的组合方案等
题目:给出n个物品的体积,要在正好装满背包的情况下求最多能装多少个,背包容量为n
#include <bits/stdc++.h>
using namespace std;
int dp[4000+1];
int main()
{
int n,a[4];
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)//初始化
dp[i]=-1;
//注意dp[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=n;j>=a[i];j++)
{
if(dp[j-a[i]]<0)//初始化在这里起的作用
continue;
dp[j]=max(dp[j],dp[j-a[i]]+1);
}
}
cout<<dp[n]<<endl;
}
注意点:初始化问题
- 0-1背包变型问题
二维变型
不开心的金明
说来惭愧,这题我第二次做居然又没做出来,笑死
变型:容量过大,将容量修改后变为两维
dp记录的是修改后背包占用容量为的情况下我选了多少个
#include<bits/stdc++.h>
using namespace std;
long long dp[330][110];
long long p[110],v[110];
int main()
{
//freopen("in.txt","r",stdin);
long long n,w;
long long minn=1e10,sum=0;
cin>>n>>w;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>p[i];
minn=minn>v[i]?v[i]:minn;
sum+=v[i];
}
minn--;
for(int i=1;i<=n;i++)
v[i]-=minn;
sum-=n*minn;
long long ans=0;
for(int i=1;i<=n;i++)
{
for(int j=sum;j>=v[i];j--)
{
for(int k=n;k>=1;k--)
{
if(j+k*minn<=w)
dp[j][k]=max(dp[j][k],dp[j-v[i]][k-1]+p[i]);
ans=max(ans,dp[j][k]);
}
}
}
cout<<ans<<endl;
return 0;
}
P1466 [USACO2.2]集合 Subset Sums
变型:价值恰好为k的方案数
第一维代表着前n件物品,第二维代表着价值,dp记录的是方案数
dp[i][j]=dp[i-1][j];没选第i件物品时价值为j的方案数自然也是考虑第i件物品时的方案基数
if(j>=a[i])
dp[i][j]+=dp[i-1][j-a[i]];
如果容量能够装下的话,应当加上前i-1件物品价值为j-a[i]的情况
#include<bits/stdc++.h>
using namespace std;
int n,a[40],dp[45][2010];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
a[i]=i;
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n*(n+1)/2;j++)
{
dp[i][j]=dp[i-1][j];
if(j>=a[i])
dp[i][j]+=dp[i-1][j-a[i]];
}
if(n*(n+1)%4==0)
cout<<dp[n][n*(n+1)/4]<<endl;
else
cout<<0<<endl;
return 0;
}
这能算是变型吗这
B. Checkout Assistant
总之背包变成了奇怪的在一定容量下的最小价值
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct node
{
int t,v;
};
node obj[2000+100];
int dp[5000];
signed main()
{
int n;
//freopen("in.txt","r",stdin);
cin>>n;
int maxxt=-1;
for(int i=1;i<=n;i++)
{
cin>>obj[i].t>>obj[i].v;
obj[i].t++;
maxxt=max(maxxt,obj[i].t);
}
maxxt+=n;
for(int i=0;i<5000;i++)
dp[i]=1e16;
dp[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=maxxt;j>=obj[i].t;j--)
{
dp[j]=min(dp[j],dp[j-obj[i].t]+obj[i].v);
}
}
int ans=1e9;
for(int i=n;i<=maxxt;i++)
{
ans=min(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
}
进阶问题
0-1背包第k优解问题
P1858 多人背包
#include<bits/stdc++.h>
using namespace std;
int k,v,n,ans,cnt,now[55];
int V[288],W[288],dp[5008][55];
int main()
{
cin>>k>>v>>n;
for(int i=0;i<=5000;i++)
for(int j=0;j<=50;j++)
dp[i][j]=-1e9;
dp[0][1]=0;
for(int i=1;i<=n;i++)
cin>>V[i]>>W[i];
for(int i=1;i<=n;i++)
{
for(int j=v;j>=V[i];j--)
{
int c1=1,c2=1,cnt=0;
while(cnt<=k)
{
if(dp[j][c1]>dp[j-V[i]][c2]+W[i])
{
now[++cnt]=dp[j][c1++];
}
else
{
now[++cnt]=dp[j-V[i]][c2++]+W[i];
}
}
for(int c=1;c<=k;c++)
{
dp[j][c]=now[c];
}
}
}
for(int i=1;i<=k;i++)
{
ans+=dp[v][i];
}
cout<<ans<<endl;
return 0;
}
3.多维0-1背包问题
完全背包
- 恰好装满完全背包问题
类似于0-1背包的恰好装满问题
#include <bits/stdc++.h>
using namespace std;
int dp[4000+1];
int main()
{
int n,a[4];
cin>>n;
for(int i=1;i<=3;i++)
cin>>a[i];
for(int i=1;i<=n;i++)//初始化
dp[i]=-1;
//注意dp[0]=0;
for(int i=1;i<=3;i++)
{
for(int j=a[i];j<=n;j++)
{
if(dp[j-a[i]]<0)//初始化在这里起的作用
continue;
dp[j]=max(dp[j],dp[j-a[i]]+1);
}
}
cout<<dp[n]<<endl;
}
- 完全背包变型问题
C. Palindrome Basis
题意:给定一个数,问这个数被回文数相加得来的不同方案数是多少
定义回文数:数字翻转后不变 比如 1,11,121
思路:由于n很小只有40000想到是否可以考虑n*n级别算法的优化,发现实际上40000个数中只有不到499个数是回文数,回文数题中说可以任意取
再看题意就是:有499种物品给定体积,每种物品无限取,求物品体积恰好是n的方案数
这样就很明确了:完全背包恰好装满情况变型
#include <bits/stdc++.h>
using namespace std;
const int N = 4e4+100;
const int mod =1e9+7;
int cnt;
int obj[N];
int dp[N][510];
bool check(int n)
{
int a[10];
int cnt1=0;
while(n)
{
a[++cnt1]=n%10;
n/=10;
}
for(int i=1,j=cnt1;i<=j;i++,j--)
{
if(a[i]!=a[j])
{
return false;
}
}
return true;
}
void get()
{
for(int i=1;i<=N;i++)
{
if(check(i))
{
obj[++cnt]=i;
}
}
}
int main()
{
get();
for(int i=1;i<=cnt;i++)
{
dp[0][i]=1;
}
for(int i=1;i<=40000+10;i++)
{
dp[i][0]=0;
for(int j=1;j<=cnt;j++)
{
if(i>=obj[j])
{
dp[i][j]=(dp[i][j-1]+dp[i-obj[j]][j])%mod;
}
else
{
dp[i][j]=dp[i][j-1]%mod;
}
}
}
int t;
for(cin>>t;t;t--)
{
int n;
cin>>n;
cout<<dp[n][cnt]%mod<<endl;
}
return 0;
}
- 多维完全背包问题(多费用)
多重背包
- 多重背包问题与优化
由于目前少见n3复杂度能够通过的题目,不加优化无疑是瞧不起出题人,所以直接讲优化。
常用优化:二进制拆分
#include<cstdio>
#include<cstring>
#include<algorithm>
#include <iostream>
using namespace std;
struct obj
{
int v,m,num;//价值。重量,件数
};
int dp[100000000+10];
int n,w;
int main()
{
freopen("in.txt","r",stdin);
obj a[110];
cin>>n>>w;
for(int i=1;i<=n;i++)
cin>>a[i].v>>a[i].m>>a[i].num;
for(int i=1;i<=n;i++)//种类外层循环
{
int num=min(a[i].num,w/a[i].m);//最多能装几个
for(int k=1;num>0;k<<=1)//二进制拆分
{
if(k>num)
k=num;
num-=k;
for(int j=w;j>=a[i].m*k;j--)//0-1背包
{
dp[j]=max(dp[j],dp[j-a[i].m*k]+a[i].v*k);
}
}
}
cout<<dp[w]<<endl;
return 0;
}
通常,二进制拆分是所有背包问题中最为好用的一种优化,极其个别的牛马出题人会严格要求时间复杂度,以至于必须用单调队列优化我看你就是在为难我胖虎 。
最佳优化:单调队列
2. 恰好装满
3. 变型
4. 多维
提问:所谓混合背包是否可以用多重背包来做,是否0-1背包和完全背包也能变成多重背包来做
分组背包
1.普通分组背包
2.分组背包变型
依赖关系
指物品之间的依赖关系然物品的拿取顺序上产生了某种结构,比如拓扑结构,树形结构这种特殊的结构就可以变为图上背包,树上背包
作为最后一个出场的背包,树上背包其实较为难理解
我们还是以一道题为例
题意:给定拓扑结构的选课关系,基于选课关系来确定如何选课获得最大学分
首先,这为什么可以作为一个树形背包?,题目中的拓扑结构