背包
01 背包
\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(只能选一次)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。
设 \(dp[i][j]\) 选取前 \(i\) 件物品重量为 \(j\) 能取得的最大的权值,可以得到转移方程 \(dp[i][j]=max(dp[i-1][j-w[i]]+val[i],dp[i-1][j]);\)
因为 i 只与 i-1 有关所以考虑滚动数组:
交替滚动
用 now 表示现在的数组,用 old 表示上一个数组,交替使用。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[3][10010];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>w[i]>>val[i];
}
int now=0,old=1;
for(int i=1;i<=m;i++){
swap(old,now);
for(int j=0;j<=t;j++){
if(j>=w[i]){
dp[now][j]=max(dp[old][j],dp[old][j-w[i]]+val[i]);
}
else{
dp[now][j]=dp[old][j];
}
}
}
cout<<dp[now][t];
return 0;
}
自我滚动
因为体积只会向小减少,所以从后向前遍历容量以确保每个数物品只选一次,上一次的数组储存的数(i-1) 结果全部就在这个数组里,这样空间压缩到一维。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[10010];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>w[i]>>val[i];
}
for(int i=1;i<=m;i++){
for(int j=t;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
完全背包
\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(可以多次同选一个)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。
我们对 01 背包的代码进行更改为从小到大枚举容量,因为在计算某个容量时,可以确保之前计算的状态已经包含了该物品之前选择的次数,这样就能够正确地累积多个相同物品的价值。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[10010];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>w[i]>>val[i];
}
for(int i=1;i<=m;i++){
for(int j=w[i];j<=t;j++){
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
多重背包
\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(给出共有几个)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。
考虑将每个物品进行二进制拆分,拆完后就转化为 01 背包问题。(因为是二进制,所以可以保证可以取到任意组合)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int t,m;
int w[N],val[N];
long long dp[N];
int main(){
ios::sync_with_stdio(false);
cin>>m>>t;
int a,b,c;
int cnt=0;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;
for(int j=1;j<=c;j<<=1){//二进制
w[++cnt]=j*a;//第cnt个背包的重量
val[cnt]=j*b;//计算权值
c-=j;//计算剩余
}
if(c){//剩余单独储存
w[++cnt]=c*a;
val[cnt]=c*b;
}
}
for(int i=1;i<=cnt;++i){//01背包选择
for(int j=t;j>=w[i];--j){
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
分组背包
01背包规则,他的物品大致可分为 \(k\) 组,每组中的物品相互冲突,现在,问最大的价值是多少。
只是加入了组的限制,只需按组储存,然后 01 背包。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[10001];
int val[10001];
int b[100001];
int g[205][205];
int dp[100001];
int main(){
ios::sync_with_stdio(false);
cin>>m>>n;
int t=0;
for(int i=1;i<=n;i++){
int x;//组
cin>>w[i]>>val[i]>>x;
t=max(t,x);//最大组
b[x]++;//小组物品增加
g[x][b[x]]=i;//第 x 组第 b[x] 个物品的编号
}
for(int i=1;i<=t;i++){//枚举组数
for(int j=m;j>=0;j--){//枚举容量
for(int k=1;k<=b[i];k++){//枚举第i组的物品
if(j>=w[g[i][k]]){
dp[j]=max(dp[j],dp[j-w[g[i][k]]]+val[g[i][k]]);
}
}
}
}
cout<<dp[m];
return 0;
}
P5322 [BJOI2019] 排兵布阵
将一个城堡看作一组
先对每组城堡中的各个玩家的敌人数进行排序
每个玩家派兵的数量*2+1 可以看作为物品重量(注意玩家派兵数量可能相同)
那么 排序后该玩家敌人数的索引与城堡索引的乘积 就是物品价值 且每个组内的物品只能选择一次
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define ls t[p].l
#define rs t[p].r
#define re register
#define pb push_back
#define pir pair<int,int>
#define f(a,x,i) for(int i=a;i<=x;i++)
#define fr(a,x,i) for(int i=a;i>=x;i--)
#define lowbit(x) x&-x;
using namespace std;
const int N=1e6+10;
const int M=2e5+4;
const int mod=1e9+7;
const int INF=1e17+7;
mt19937 rnd(251);
int n,q,m;
int a[105][105];
int f[N];
void solve(){
cin>>n>>q>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=q;j++){
cin>>a[j][i];
}
}
for(int i=1;i<=q;i++){
sort(a[i]+1,a[i]+1+n);
}
for(int i=1;i<=q;i++){
for(int j=m;j>=0;j--){
for(int k=1;k<=n;k++){
if(j>=a[i][k]*2+1){
f[j]=max(f[j],f[j-a[i][k]*2-1]+i*k);
}
}
}
}
cout<<f[m];
}
signed main(){
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(nullptr);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
混合背包
背包中的物品有的可以选一次(0/1 背包),有的可以选多次(完全背包),有的数量有限(多重背包)。
无限个并不是真的无限,只需转化为最大容积除物品中重量的个数就行,然后问题就转化为多重背包了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int t,m;
int w[N],val[N];
long long dp[N];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
int a,b,c;
int cnt=0;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;//重,价,数
if(c==0){//无限个(最大容积除重量个)
c=t/a;
}
for(int j=1;j<=c;j<<=1){//剩下多重背包
w[++cnt]=j*a;
val[cnt]=j*b;
c-=j;
}
if(c){
w[++cnt]=c*a;
val[cnt]=c*b;
}
}
for(int i=1;i<=cnt;++i){
for(int j=t;j>=w[i];--j){//01背包
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
P1779 魔鬼杀手
背包的用法
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define ls t[p].l
#define rs t[p].r
#define re register
#define pb push_back
#define pir pair<int,int>
#define f(a,x,i) for(int i=a;i<=x;i++)
#define fr(a,x,i) for(int i=a;i>=x;i--)
#define lowbit(x) x&-x;
using namespace std;
const int N=3e6+10;
const int M=2e6+5;
const int mod=1e9+7;
const int INF=1e17+7;
mt19937 rnd(251);
int n,m;
int a[N];
int bv[N],cv[N];
int bt[N],ct[N];
int cnt1=0,cnt2=0;
int f1[N],f2[N];
int check(int x){
int sum=0;
for(int i=1;i<=n;i++){
if(a[i]-x>0){
sum+=f2[a[i]-x];
}
}
return sum;
}
void solve(){
for(int i=1;i<=M;i++){
f1[i]=f2[i]=INF;
}
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
cin>>m;
for(int i=1;i<=m;i++){
string sd,sf;
int x,y;
cin>>sd>>x>>sf>>y;
if(x==0&&y!=0){
cout<<0;
return;
}
if(sf=="All"){
cnt1++;
bt[cnt1]=min(y,M);
bv[cnt1]=x;
}
else{
cnt2++;
ct[cnt2]=min(y,M);
cv[cnt2]=x;
}
}
for(int i=1;i<=cnt1;i++){
for(int j=bt[i];j<=M;j++){
f1[j]=min(f1[j],f1[j-bt[i]]+bv[i]);
}
}
for(int i=1;i<=cnt2;i++){
for(int j=ct[i];j<=M;j++){
f2[j]=min(f2[j],f2[j-ct[i]]+cv[i]);
}
}
for(int j=M-1;j>=0;j--){
f2[j]=min(f2[j],f2[j+1]);
}
int ans=INF;
for(int j=0;j<=M;j++){
ans=min(ans,f1[j]+check(j));
}
cout<<ans;
}
signed main(){
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(nullptr);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}

浙公网安备 33010602011771号