背包 学习笔记
背包 学习笔记
甚至到退役都没有系统地学习过这个东西,唉,草台班子SDZX。
01背包
到高中毕业也只会这一种。。
不过状态转移方程还是很好写,注意如果要滚掉一维,直接倒序枚举容量即可。
例题 P1048
for(int i=1;i<=n;++i)
{
for(int w=m;w>=0;--w)
{
if(w-a[i]>=0)dp[w]=max(dp[w],dp[w-a[i]]+val[i]);
}
}
存在性背包
P1441 一点小变式,但是大概就是把01背包的转移改成了赋一个布尔值,回过头发现还没学的时候做出来一道新生赛的题竟然就是这玩意
//存在性01背包 P1441
#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void re(T &x)
{
x=0;int f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
}
int MAXVAL;
int n,m,a[100],w[100];
bool tag[100];
int ans;
inline void dfs(int finished,int st)
{
if(finished==m||st>n)
{
if(finished!=m)return ;
int cnt=0;
for(int i=1;i<=n;++i)if(!tag[i])w[++cnt]=a[i];
vector<int>dp (MAXVAL+10,0);
dp[0]=1;
for(int i=1;i<=cnt;++i)
for(int j=MAXVAL;j>=w[i];--j)
if(dp[j-w[i]])dp[j]=1;
ans=max(ans,accumulate(dp.begin(),dp.end(),-1));
return ;
}
for(int i=st;i<=n;++i)
{
tag[i]=1;
dfs(finished+1,i+1);
tag[i]=0;
}
}
int main()
{
re(n),re(m);
for(int i=1;i<=n;++i)re(a[i]),MAXVAL+=a[i];
dfs(0,1);
cout<<ans;
return 0;
}
完全背包
每个物品可以无限选,那么比较naive的想法就是再在最里面套一层枚举选取个数的循环,但奈何这种时间复杂度较高,想一想有没有更加优秀的做法。
为什么01背包不能正序枚举?因为可能会出现 \(m\) 容量时,我已经用 当前物品做了一次转移,也就是已经被用了,但是后面枚举 \(m+w_i\) 这个容量的时候,可能又会从 \(m\) 这个状态 再装一个当前物品进行转移,这样从物理意义上来说,就是用了两次同一个物品,在 01背包的意义下明显是不合法的。
但是这种不合法恰恰就正好是完全背包的要求所在啊!!!所以我们正序枚举就行。
for(int i=1;i<=n;++i)
{
for(int j=w[i];j<=m;++j)
{
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
多重背包
不是无限个,可以选多次。
暴力地话就是完全背包的naive做法。复杂度是 \(O(nmk)\) 的。
for(int i=1;i<=n;++i)
{
for(int j=m;j>=0;--j)
{
for(int cnt=0;cnt<=num[i]&&j-cnt*w[i]>=0;++cnt)
{
dp[j]=max(dp[j],dp[j-cnt*w[i]]+val[i]*cnt);
}
}
}
二进制拆分可以减少无意义选取的次数,注意这里要求拆分的方式能够组合出所有数:复杂度 \(O(m\sum k_i)\)
for(int i=1,tval,tw,tnum;i<=n;++i)
{
re(tval),re(tw),re(tnum);
for(int j=1;j<=tnum;j<<=1)
{
w[++cnt]=tw*j,val[cnt]=tval*j;
tnum-=j;
}
if(tnum)w[++cnt]=tw*tnum,val[cnt]=tval*tnum;
}
for(int i=1;i<=cnt;++i)
{
for(int j=m;j>=w[i];--j)
{
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
还有一种比较优秀的单调队列优化,但是今晚害得做一做 cf ,挖个坑。
然而昨天晚上还是没有做 : (
//TODO
例题 P1776
分组背包
每组里面只能选取一件,直接枚举组数->枚举容量->枚举每个子物品就可以了。
for(int i=1;i<=t;++i)
{
for(int j=m;j>=0;--j)
{
for(int k=1;k<=a[i].cnt;++k)
{
if(j-a[i].w[k]>=0)dp[j]=max(dp[j],dp[j-a[i].w[k]]+a[i].val[k]);
}
}
}
费用背包
多了一维费用,实际上就是两维的背包。
for(int i=1;i<=n;++i)
{
cin>>w[i]>>t[i];
for(int j=m;j>=w[i];--j)
{
for(int k=T;k>=t[i];--k)
{
dp[j][k]=max(dp[j][k],dp[j-w[i]][k-t[i]]+1);
}
}
}
例题 1855
依赖背包
言下之意就是存在某些物品 \(B\) ,如果要选它 ,那么必须先选另外一个指定的物品 \(A\) 。
发现这样的话无非就要么只买主件,要么就同时买其中若干个附件,但是由于这些情况又不能够同时成立,所以就转化成了一个分组背包的模型。
例题 P 1064
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
int m,n;
int weight[N],val[N],son[N][4],cnt[N],q[N];
int num=0;
struct group
{
int w[10],v[10];
int num;
void display()
{
for(int i=1;i<=num;++i)
{printf("%d %d\n",w[i],v[i]);}
}
}a[N];
inline void pre()
{
cin>>m>>n;
for(int i=1;i<=n;++i)
{
cin>>weight[i]>>val[i];
val[i]*=weight[i];
cin>>q[i];
if(q[i]==0)continue;
son[q[i]][++cnt[q[i]]]=i;
}
//0的cnt可能大于2了,这样就会访问到别的内存!!
for(int i=1;i<=n;++i)
{
if(q[i])continue;
num++;
a[num].num++;
a[num].w[a[num].num]=weight[i],a[num].v[a[num].num]=val[i];
if(cnt[i]>=1)
{
a[num].num++;
a[num].w[a[num].num]=weight[i]+weight[son[i][1]],a[num].v[a[num].num]=val[i]+val[son[i][1]];
}
if(cnt[i]==2)
{
a[num].num++;
a[num].w[a[num].num]=weight[i]+weight[son[i][2]],a[num].v[a[num].num]=val[i]+val[son[i][2]];
a[num].num++;
a[num].w[a[num].num]=weight[i]+weight[son[i][1]]+weight[son[i][2]],a[num].v[a[num].num]=val[i]+val[son[i][1]]+val[son[i][2]];
}
}
}
int main()
{
pre();
vector<int>dp(m+1,-1);
dp[0]=0;
for(int i=1;i<=num;++i)
{
for(int j=m;j>=0;--j)
{
for(int idx=1;idx<=a[i].num;++idx)
{
if(j-a[i].w[idx]>=0)dp[j]=max(dp[j],dp[j-a[i].w[idx]]+a[i].v[idx]);
}
}
}
int ans=0;
for(int i=0;i<=m;++i)ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
树形背包
顾名思义就是在数上面跑一个背包,以每一个节点为根节点,其子树都是一个独立的背包问题。
然后这个子树算出来的 dp 值作为新的物品价值 来当做这个节点的父节点的待选物,这样对于父节点来说又是一个新的dp问题
例题 P2014 选课
//P2014选课
#include<bits/stdc++.h>
using namespace std;
inline void ckmax(int &x,int y){if(x<y)x=y;}
const int N=400;
int n,m;
int dp[N][N],s[N];
vector<int> e[N];
inline void pre()
{
cin>>n>>m;
for(int i=1,fa;i<=n;++i)
{
cin>>fa>>s[i];
e[fa].push_back(i);
}
}
inline void dfs(int x)
{
for(auto v:e[x])
dfs(v);
for(auto v:e[x])
{
for(int j=m;j>=1;--j)
{
for(int k=0;k<=j-1;++k)
{
ckmax(dp[x][j],dp[x][j-(k+1)]+dp[v][k]+s[v]);
}
}
}
}
int main()
{
pre();
dfs(0);
int ans=0;
for(int i=0;i<=m;++i)ckmax(ans,dp[0][i]);
cout<<ans;
return 0;
}
例题 们
BJTU2038
1.分组背包,不一定要用结构体把所有情况构造出来,这启示我们可以直接从状态压缩的角度枚举二进制数来进行考虑
2.但是容量的转移并不是传统的加减,因为回合结束还有一个容量自动增加,所以还有一个增量的过程,要额外做一个映射再进行转移。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
const ll INF=1e15;
ll val[10],w[10];
pair<ll,ll> sum(int sta)
{
ll ansval=0,answ=0;
int cnt=1;
while(sta)
{
ansval+=(sta&1ll)*val[cnt];
answ+=(sta&1ll)*w[cnt];
sta>>=1;
cnt++;
}
return {ansval,answ};
}
int las[1000];
//dp[j] 剩下j个金币的方案数
int main()
{
cin>>n;
vector< vector<ll> > dp(20000,vector<ll>(100,-INF));
for(int i=1;i<=61;++i)las[i+i/10+2]=i;
for(int i=0;i<=70;++i)if(!las[i])las[i]=999;
//非常重要的一个地方,因为有些状态注定根本就没有前继状态,是不合法的,但是转移的时候会从0转移,但是0又是一个合法状态
for(int j=1;j<=5;++j)cin>>val[j];
for(int j=1;j<=5;++j)cin>>w[j];
for(int j=24;j>=0;--j)
{
for(int sta=0;sta<32;++sta)
{
pair<ll,ll> p=sum(sta);
ll nowval=p.first,noww=p.second;
if(j+noww!=24)continue;
dp[1][j]=max(dp[1][j],nowval);
}
}
for(int i=2;i<=n;++i)
{
for(int j=1;j<=5;++j)cin>>val[j];
for(int j=1;j<=5;++j)cin>>w[j];
for(int j=70;j>=0;--j)
{
for(int sta=0;sta<32;++sta)
{
pair<ll,ll> p=sum(sta);
ll nowval=p.first,noww=p.second;
if(j+noww>70)continue;
if(j+noww<=69)
dp[i][j]=max(dp[i][j],dp[i-1][las[j+noww]]+nowval);
else
for(
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,k;
const int N=1e4+10;
struct item
{
int w,v,c;
const bool operator<(item tmp){return c<tmp.c;}
}a[N];
//\sum w+ types*k
const int INF=1e9;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>x>>k;
for(int i=1;i<=n;++i)
{
cin>>a[i].w>>a[i].v>>a[i].c;
}
sort(a+1,a+n+1);
// for(int i=1;i<=n;++i)cout<<a[i].w<<' '<<a[i].v<<' '<<a[i].c<<'\n';
a[0].c=0;
vector<vector<vector<int>>> dp(2,vector<vector<int>>(x+10,vector<int>(2,-INF)));
int p=0;
dp[0][0][0]=0;
for(int i=1;i<=n;++i)
{
p^=1;
for(int j=0;j<=x;++j)
{
if(a[i].c!=a[i-1].c)
{
if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0],dp[p^1][j-a[i].w][1])+a[i].v+k;
dp[p][j][0]=max(dp[p^1][j][0],dp[p^1][j][1]);
}
else
{
if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0]+a[i].v+k,dp[p^1][j-a[i].w][1]+a[i].v);
dp[p][j][1]=max(dp[p][j][1],dp[p^1][j][1]);
dp[p][j][0]=dp[p^1][j][0];
}
// cout<<"DP"<<i<<","<<j<<":"<<dp[i][j][0]<<' '<<dp[i][j][1]<<'\n';
}
}
int ans=0;
for(int i=0;i<=x;++i)ans=max(ans,max(dp[p][i][0],dp[p][i][1]));
cout<<ans;
return 0;
}int t=62;t<=70;++t)dp[i][j]=ma```c
```x(dp[i][j],dp[i-1][t]+nowval);//62-70
}
}
}
ll ans=0;
for(int i=0;i<=70;++i)ans=max(ans,dp[n][i]);
cout<<ans;
return 0;
}
ABC383F
很明显的一个背包问题,但是带有限制,也就是所谓的“颜色”。
由于物品的考虑顺序并不会影响最终的答案,所以可以把所有物品都按照颜色排序,这样就保证了 同一种颜色都相邻
这时候定义 \(dp[i][j][0/1]\) 为前 \(i\) 个达到 \(j\) 容量,当前这个颜色选过没有。
注意这个时候转移可能有些不同,比如 \(dp[i][j][1]\) 实际上可以是当前这个不选,之前选了一个的情况,所以转移方程可能有点不同。
然后不能开三维,但是直接kick一维似乎也不太行??那就把第一维滚掉吧。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,k;
const int N=1e4+10;
struct item
{
int w,v,c;
const bool operator<(item tmp){return c<tmp.c;}
}a[N];
//\sum w+ types*k
const int INF=1e9;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>x>>k;
for(int i=1;i<=n;++i)
{
cin>>a[i].w>>a[i].v>>a[i].c;
}
sort(a+1,a+n+1);
// for(int i=1;i<=n;++i)cout<<a[i].w<<' '<<a[i].v<<' '<<a[i].c<<'\n';
a[0].c=0;
vector<vector<vector<int>>> dp(2,vector<vector<int>>(x+10,vector<int>(2,-INF)));
int p=0;
dp[0][0][0]=0;
for(int i=1;i<=n;++i)
{
p^=1;
for(int j=0;j<=x;++j)
{
if(a[i].c!=a[i-1].c)
{
if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0],dp[p^1][j-a[i].w][1])+a[i].v+k;
dp[p][j][0]=max(dp[p^1][j][0],dp[p^1][j][1]);
}
else
{
if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0]+a[i].v+k,dp[p^1][j-a[i].w][1]+a[i].v);
dp[p][j][1]=max(dp[p][j][1],dp[p^1][j][1]);//这个地方容易考虑漏
dp[p][j][0]=dp[p^1][j][0];
}
// cout<<"DP"<<i<<","<<j<<":"<<dp[i][j][0]<<' '<<dp[i][j][1]<<'\n';
}
}
int ans=0;
for(int i=0;i<=x;++i)ans=max(ans,max(dp[p][i][0],dp[p][i][1]));
cout<<ans;
return 0;
}
本文来自博客园,作者:Hanggoash,转载请注明原文链接:https://www.cnblogs.com/Hanggoash/p/18561527