1.01背包
1.1.DFS记忆化搜索
$O(wn)$ 洛谷P1048
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #define N 1005 #define W 105 ll v[N]={0},w[N]={0};z ll mem[W][N]={0}; ll maxw,n; ll dfs(ll i,ll curw){ if(mem[i][curw]) return mem[i][curw]; if(i>n) return mem[i][curw]=0; if(curw>=w[i]) return mem[i][curw]=max(dfs(i+1,curw-w[i])+v[i],dfs(i+1,curw)); else return mem[i][curw]=dfs(i+1,curw); } void solve() { cin >> maxw >> n; FORLL(i,1,n) cin >> w[i] >> v[i]; cout << dfs(1,maxw) << endl; }
|
1.2.二维数组
$O(wn) M(wn)$ 洛谷P1048
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #define N 1005 #define W 105 ll v[N]={0},w[N]={0}; ll dp[N][W]={0}; ll n,maxw; void solve() { cin >> maxw >> n; FORLL(i,1,n) cin >> w[i] >> v[i]; FORLL(i,1,n) FORLL(j,0,maxw){ if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]); else dp[i][j]=dp[i-1][j]; } cout << dp[n][maxw] << endl; }
|
1.3.一维滚动数组
$O(wn) M(w)$ 洛谷P2871
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define N 5005 #define W 20005 ll v[N]={0},w[N]={0}; ll dp[W]={0}; ll n,maxw; void solve() { cin >> n >> maxw; FORLL(i,1,n) cin >> w[i] >> v[i]; FORLL(i,1,n) FORLL_rev(j,maxw,w[i]){ dp[j]=max(dp[j-w[i]]+v[i],dp[j]); } cout << dp[maxw] << endl; }
|
2.完全背包
2.1.一维滚动数组
$O(wn) M(w)$ 洛谷P1616
和01背包唯一区别在剩余容量从小到大遍历,每个物品能取多次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define N 10005 #define W 10000005 ll v[N]={0},w[N]={0}; ll dp[W]={0}; ll n,maxw; void solve() { cin >> maxw >> n; FORLL(i,1,n) cin >> w[i] >> v[i]; FORLL(i,1,n) FORLL(j,w[i],maxw){ dp[j]=max(dp[j-w[i]]+v[i],dp[j]); } cout << dp[maxw] << endl; }
|
2.2.贪心优化
$O(wn) M(w)$ 洛谷P1616
贪心思想:对于两件物品 $i,j$ ,如果 $w_i \le w_j \And v_i \ge v_j$ ,则只需保留 $i$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #define N 10005 #define W 10000005 vector<pll> objs,tv; ll dp[W]={0}; ll n,maxw,w,v; void solve() { cin >> maxw >> n; tv.resize(n); for(auto &x:tv) cin >> x.first >> x.second; SORT(tv);ll maxv=-1; for(auto x:tv) if(x.second>maxv) {objs.emplace_back(x);maxv=x.second;} for(auto x:objs){ w=x.first;v=x.second; FORLL(j,w,maxw){ dp[j]=max(dp[j-w]+v,dp[j]); } } cout << dp[maxw] << endl; }
|
3.多重背包
3.1.朴素方法
$O(w\sum cnt_i)$
朴素方法:按有 $cnt_i$ 个的物品 $i$ ,进行01背包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #define N 100005 #define W 100005 ll v[N]={0},w[N]={0}; ll dp[W]={0}; ll n=0,maxw,tn; void solve() { cin >> tn >> maxw; ll tv,tw,cnt,n=0; FORLL(i,1,tn){ cin >> tv >> tw >> cnt; FORLL(j,1,cnt){ v[++n]=tv; w[n]=tw; } } FORLL(i,1,n) FORLL_rev(j,maxw,w[i]) dp[j]=max(dp[j-w[i]]+v[i],dp[j]); cout << dp[maxw] << endl; }
|
3.2.二进制分组
$O(w\sum\lg{cnt_i})$
二进制分组优化:对于每个物品,将其按二进制分组,捆绑一个物品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #define N 100005 #define W 100005 ll v[N]={0},w[N]={0}; ll dp[W]={0}; ll n=0,maxw,tn; void solve() { cin >> tn >> maxw; ll tv,tw,cnt,n=0; FORLL(i,1,tn){ cin >> tv >> tw >> cnt; ll b=1; while(cnt>b){ v[++n]=tv*b; w[n]=tw*b; cnt-=b;b*=2; } if(cnt) {v[++n]=tv*cnt;w[n]=tw*cnt;} } FORLL(i,1,n) FORLL_rev(j,maxw,w[i]) dp[j]=max(dp[j-w[i]]+v[i],dp[j]); cout << dp[maxw] << endl; }
|
4.混合背包
$O(wn)$ 洛谷P1833
01背包、多重背包和完全背包的缝合怪//
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #define N 100005 #define W 100005 ll v[N]={0},w[N]={0}; int cnt[N]={0}; ll dp[W]={0}; ll n=0,maxw,tn; void solve() { ll h1,m1,h2,m2;char tc; cin >> h1 >> tc >> m1 >> h2 >> tc >> m2 >> tn; maxw=abs((h2*60+m2)-(h1*60+m1)); ll tv,tw,tcnt,n=0; FORLL(i,1,tn){ cin >> tw >> tv >> tcnt; if(tcnt){ ll b=1; while(tcnt>b){ v[++n]=tv*b; w[n]=tw*b; cnt[n]=b; tcnt-=b;b*=2; } if(tcnt) {v[++n]=tv*tcnt;w[n]=tw*tcnt;cnt[n]=1;} }else{ v[++n]=tv; w[n]=tw; cnt[n]=0; } } FORLL(i,1,n) if(cnt[i]){ FORLL_rev(j,maxw,w[i]) dp[j]=max(dp[j-w[i]]+v[i],dp[j]); }else{ FORLL(j,w[i],maxw) dp[j]=max(dp[j-w[i]]+v[i],dp[j]); } cout << dp[maxw] << endl; }
|
5.二维费用背包
$O(nw_1w_2)$ 洛谷P1855
具有两种费用属性的背包问题,以01背包为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #define N 105 #define W 205 ll v[N]={0},w1[N]={0},w2[N]={0}; ll dp[W][W]={0}; ll n,maxw1,maxw2; void solve() { cin >> n >> maxw1 >> maxw2; FORLL(i,1,n){ cin >> w1[i] >> w2[i]; v[i]=1; } FORLL(i,1,n) FORLL_rev(j1,maxw1,w1[i]) FORLL_rev(j2,maxw2,w2[i]) dp[j1][j2]=max(dp[j1-w1[i]][j2-w2[i]]+v[i],dp[j1][j2]); cout << dp[maxw1][maxw2] << endl; }
|
6.分组背包
$O(nw)$ 洛谷P1757
01背包的进化体,每个组中最多能取1个物品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #define N 65536 ll maxw,n; ll v[105][N]={0},w[105][N]={0},dp[1000*N]={0}; ll cnt[105]={0},mxid=0; void solve() { cin >> maxw >> n; ll tv,tw,id; FORLL(i,1,n){ cin >> tw >> tv >> id; cnt[id]++; v[id][cnt[id]]=tv; w[id][cnt[id]]=tw; mxid=max(mxid,id); } FORLL(k,1,mxid) FORLL_rev(j,maxw,0) FORLL(i,1,cnt[k]) if(j>=w[k][i]) dp[j]=max(dp[j-w[k][i]]+v[k][i],dp[j]); cout << dp[maxw] << endl; }
|
7.有依赖的背包
$O(nw)$ 洛谷P1064
分组背包的进化体,将所有主副件组合方案作为一组进行分组背包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #define W 32005 #define N 61 ll maxw,n; ll w[N][4]={0},v[N][4]={0},dp[W]={0},cnt[N]={0}; vector<ll> id_list; void solve() { cin >> maxw >> n; ll tw,tp,id; FORLL(i,1,n){ cin >> tw >> tp >> id; if(id==0){ id=i;w[id][0]=tw;v[id][0]=tw*tp; id_list.emplace_back(i); }else if(cnt[id]==0){ cnt[id]++; w[id][1]=tw+w[id][0]; v[id][1]=tw*tp+v[id][0]; }else if(cnt[id]==1){ cnt[id]++; w[id][2]=tw+w[id][0]; v[id][2]=tw*tp+v[id][0]; w[id][3]=tw+w[id][1]; v[id][3]=tw*tp+v[id][1]; } } for(auto k:id_list){ ll t=0; if(cnt[k]==1) t=1; if(cnt[k]==2) t=3; FORLL_rev(j,maxw,0) FORLL(i,0,t) if(j>=w[k][i]) dp[j]=max(dp[j-w[k][i]]+v[k][i],dp[j]); } cout << dp[maxw] << endl; }
|
8.进阶问题
1.求具体方案
$O(wn)$
以完全背包为例,在转移时记录容量j下选择的物品编号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #define N 10005 #define W 10000005 ll v[N]={0},w[N]={0},cnt[N]={0}; ll dp[W]={0},g[W]={0}; ll n,maxw; void solve() { cin >> maxw >> n; FORLL(i,1,n) cin >> w[i] >> v[i]; FORLL(i,1,n) FORLL(j,w[i],maxw){ if(dp[j-w[i]]+v[i]>dp[j]){ dp[j]=dp[j-w[i]]+v[i]; g[j]=i; } } cout << dp[maxw] << endl; ll curw=maxw; while(g[curw]){ cnt[g[curw]]++; curw-=w[g[curw]]; } FORLL(i,1,n) ; }
|
2.装满方案计数
对于给定的一个背包容量、物品费用、其他关系等的问题,求装到一定容量的方案总数。
2.1.不考虑顺序
$O(wn)$
不同的选择顺序看作相同方案。以01背包为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define N 5005 #define W 20005 ll v[N]={0},w[N]={0}; ll dp[W]={1,0}; ll n,maxw; void solve() { cin >> n >> maxw; FORLL(i,1,n) cin >> w[i] >> v[i]; FORLL(i,1,n) FORLL_rev(j,maxw,w[i]){ dp[j]=dp[j]+dp[j-w[i]]; } cout << dp[maxw] << endl; }
|
2.2.考虑顺序
$O(wn)$ ZJNU C1299_B
不同的选择顺序看作不同方案。以完全背包为例
从 $0$ 到 $n$ ,每次可以前进 {1,2,4} ,求方案数
1 2 3 4 5 6 7 8 9 10 11 12
| #define W 205 ll w[4]={0,1,2,4}; ll dp[W]={1,0}; ll maxw; void solve() { cin >> maxw; FORLL(j,1,maxw) FORLL(i,1,3) if(j>=w[i]) dp[j]=dp[j]+dp[j-w[i]]; cout << dp[maxw] << endl; }
|
3.最优方案计数
$O(wn)$
求最优背包方案数,以01背包为例,g[j]代表容量j下最优方案数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #define N 5005 #define W 20005 ll v[N]={0},w[N]={0}; ll dp[W]={0},g[W]={1,0}; ll n,maxw; void solve() { cin >> n >> maxw; FORLL(i,1,n) cin >> w[i] >> v[i]; FORLL(i,1,n) FORLL_rev(j,maxw,w[i]) if(dp[j-w[i]]+v[i]>dp[j]){ dp[j]=dp[j-w[i]]+v[i]; g[j]=g[j-w[i]]; }else if(dp[j-w[i]]+v[i]==dp[j]) g[j]+=g[j-w[i]]; cout << dp[maxw] << ' ' << g[maxw] << endl; }
|
4.求第k优解
$O(wnk)$ HDU2639
求背包的第k优解,以01背包为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #define N 105 #define W 1005 void solve() { ll v[N]={0},w[N]={0}; ll dp[W][35]={0}; ll a[35]={0},b[35]={0}; ll n,maxw,tark; ll x,y,z; cin >> n >> maxw >> tark; a[tark+1]=b[tark+1]=-1; FORLL(i,1,n) cin >> v[i]; FORLL(i,1,n) cin >> w[i]; FORLL(i,1,n) FORLL_rev(j,maxw,w[i]){ FORLL(k,1,tark){ a[k]=dp[j-w[i]][k]+v[i]; b[k]=dp[j][k]; } x=y=z=1; while(z<=tark&&!(a[x]==-1&&b[y]==-1)){ if(a[x]>b[y]) dp[j][z]=a[x++]; else dp[j][z]=b[y++]; if(dp[j][z]!=dp[j][z-1]) z++; } } cout << dp[maxw][tark] << endl; }
|
Reference:OI-Wiki