01 背包
01背包问题
题目链接:
U225269 01背包问题 - 洛谷
思路:
设 f[i][j] 表示前 i 件物品放入容量为 j 的背包的最大价值。
如果当前物品不能放入, 此时没得选了,只能从前 i-1 件物品最优解转移过来,则 f[i][j]=f[i-1][j]。
如果能放入则比较价值:f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const int N=1050; int v[N],w[N]; int f[N][N]; void solve() { int n,m; cin>>m>>n; for(int i=1;i<=n;i++) { cin>>v[i]>>w[i]; } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(j<v[i])f[i][j]=f[i-1][j]; else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]); } } cout<<f[n][m]<<endl; }
|
现在在时间上无法优化了,还可以优化空间。
可以将 f[i][j] 优化到一维 f[j] 表示 N 件物品,背包容量为 j 下的最优解。
为什么可以这样优化,可以看出每次求 f[i][j] 只依赖于 f[i-1][],所以不需要保留之前的 f[i-2][] 等状态值。
一维情况下需要逆序遍历背包容量。当我们更新索引值较大的值时,需要用到之前的状态 f[j-v[i]],要保证这个状态不被更新掉,例如我们在第 i 轮对体积为 4 的物品进行决策,则状态 f[8] 由 f[4] 更新而来,这里的 f[4] 应该是 f[i-1][4],如果是正序枚举的话会变为 f[i][4],所以我们需要逆序更新,使 f[4] 还没有在第 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 25 26 27 28 29 30 31 32
| for(int i=1;i<=n;i++) { for(int j=m;j>=0;j--) { if(j<v[i])f[j]=f[j]; else{ f[j]=max(f[j],f[j-v[i]]+w[i]); } } }
for(int i=1;i<=n;i++) { for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } }
for(int i=1;i<=n;i++) { cin>>v>>w; for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v]+w); } }
|
采药
题目链接:
P1048 [NOIP 2005 普及组] 采药 - 洛谷
思路:
在这个题目中每种草药只有一株,时间 T 就是背包的容量,要实现价值的最大化。所以上一题代码即可通过。
代码:
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 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=1050; int v[N],w[N]; int f[N]; void solve() { int n,m; cin>>m>>n; for(int i=1;i<=n;i++) { cin>>v[i]>>w[i]; for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]<<endl; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
装箱问题
题目链接:
P1049 [NOIP 2001 普及组] 装箱问题 - 洛谷
思路:
要使箱子的剩余空间最小,那么就是使箱子的使用空间最大,也就是在不超过箱子体积的情况下,使其使用空间尽可能大,我们可以把 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
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=2e5+5; int v,w; int f[N]; void solve() { int n,m; cin>>m>>n; for(int i=1;i<=n;i++) { cin>>v; for(int j=m;j>=v;j--){ f[j]=max(f[j],f[j-v]+v); } } cout<<m-f[m]<<endl; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
背包问题求方案数
题目链接:
U224067 背包问题求方案数 - 洛谷
代码:
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 40 41
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=1050; int v[N],w[N]; int f[N][N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>v[i]>>w[i]; } for(int i=n;i;i--) { for(int j=0;j<=m;j++) { f[i][j]=f[i+1][j]; if(j>=v[i])f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]); } } int i=1,j=m; while(i<=n){ if(j>=v[i]&&f[i+1][j-v[i]]+w[i]>=f[i+1][j]){ cout<<i<<" "; j-=v[i]; i++; }else i++; } } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
背包问题求方案数
题目链接:
U224067 背包问题求方案数 - 洛谷
思路:
设 f[i] 表示背包容量为 i 时能装入物品的最大价值,c[i] 表示背包容量为 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 25
| const int N=1010; const int MOD=1e9+7; int f[N]; int c[N]; void solve() { int n,m; cin>>n>>m; int v,w; for(int i=0;i<=m;i++)c[i]=1; for(int i=1;i<=n;i++) { cin>>v>>w; for(int j=m;j>=v;j--) { if(f[j-v]+w>f[j]){ f[j]=f[j-v]+w; c[j]=c[j-v]; }else if(f[j-v]+w==f[j]){ c[j]=(c[j-v]+c[j])%MOD; } } } cout<<c[m]; }
|
背包问题求具体方案
题目链接:
U224072 背包问题求具体方案 - 洛谷
思路:
为了确保字典序最小,那么肯定是优先选前面的,所以我们从后向前遍历,让最优解落在 f[1][m],然后从第一个开始向后搜索字典序 1 最小的路径方案。
设 f[i][j] 为从第 i 个物品到最后一个物品装入容量为 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 25 26 27 28
| const int N=1050; int v[N],w[N]; int f[N][N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>v[i]>>w[i]; } for(int i=n;i;i--) { for(int j=0;j<=m;j++) { f[i][j]=f[i+1][j]; if(j>=v[i])f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]); } } int i=1,j=m; while(i<=n){ if(j>=v[i]&&f[i+1][j-v[i]]+w[i]>=f[i+1][j]){ cout<<i<<" "; j-=v[i]; i++; }else i++; } }
|
数字组合
题目链接:
信息学奥赛一本通(C++版)在线评测系统
思路:
思路见代码注释。
代码:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=150; int a[N]; int f[N][10010];
void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>a[i]; f[i][0]=1; } f[0][0]=1; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { f[i][j]+=f[i-1][j]; if(j>=a[i])f[i][j]+=f[i-1][j-a[i]]; } } cout<<f[n][m]<<endl; } signed main(){ IOS int t=1; while(t--) { solve(); } }
void solve() { int n,m,v; cin>>n>>m; f[0]=1; for(int i=1;i<=n;i++) { cin>>v; for(int j=m;j>=v;j--) { f[j]+=f[j-v]; } } cout<<f[m]; }
|
开心的金明
题目链接:
P1060 [NOIP 2006 普及组] 开心的金明 - 洛谷
思路:
01 背包
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const int N=2e5+5; int f[N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=m;i++) { int v,p; cin>>v>>p; p*=v; for(int j=n;j>=v;j--){ f[j]=max(f[j],f[j-v]+p); } } cout<<f[n]; }
|
二维费用背包
二维费用的背包问题
题目链接:
T426845 二维费用的背包问题 - 洛谷
代码:
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
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=2e5+5; int v[N],w[N],m[N]; int f[110][110]; void solve() { int n,V,M; cin>>n>>V>>M; for(int i=1;i<=n;i++) { cin>>v[i]>>m[i]>>w[i]; } for(int i=1;i<=n;i++) { for(int j=V;j>=v[i];j--) { for(int k=M;k>=m[i];k--) { f[j][k]=max(f[j][k],f[j-v[i]][k-m[i]]+w[i]); } } } cout<<f[V][M]; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
宠物小精灵之收服
题目链接:
U266184 宠物小精灵之收服 - 洛谷
思路:
二维费用 01背包,精灵球数量为第一费用,消耗的体力值为第二费用,最后要捕获的精灵数量最多。
设 f[i][j][k] 表示考虑前 i 个物品,第一维费用不超过 j 第二维费用不超过 k 的方案的最大价值。
分为选和不选两种情况:f[i][j][k]=max(f[i-1][j][k],f[i-1][j-v1][k-v2]+1)。
之后在遍历一遍求得满足最大数量情况下的最大体力值。
三维数组是一定会超内存的,所以优化为二维滚动数组去掉表示物品数量的 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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=2e5+5; int v[N],w[N]; int f[1100][550]; void solve() { int n,m,t; cin>>n>>m>>t; for(int i=1;i<=t;i++) { cin>>v[i]>>w[i]; } for(int i=1;i<=t;i++) { for(int j=n;j>=v[i];j--) { for(int k=m-1;k>=w[i];k--) { f[j][k]=max(f[j][k],f[j-v[i]][k-w[i]]+1); } } } cout<<f[n][m-1]<<" "; for(int i=0;i<m-1;i++) { if(f[n][i]==f[n][m-1]){ cout<<m-i; break; } } } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
潜水员
题目链接:
U291791 潜水员 - 洛谷
思路:
这个题目和之前的差别是要求不少于费用。所以设 f[i][j][k] 表示考虑前 i 个物品,第一维费用不低于 j,第二维费用不低于 k 的方案。
在枚举氮气和氧气费用时不需要设限了。因为可以超载。
状态转移公式:
不选的话就是 f[i][j][k]=f[i-1][j][k]。
选 f[i][j][k]=min(f[i][j][k],f[i-1][j-v1][k-v2]) 注意这里的下标不能低于 0。
仍然优化空间。
代码:
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 40
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=2e5+5; int v[N],w[N],m[N]; int f[110][110]; void solve() { int n,M; cin>>n>>M; int t; cin>>t; memset(f,0x3f,sizeof f); for(int i=1;i<=t;i++) { cin>>v[i]>>m[i]>>w[i]; } f[0][0]=0; for(int i=1;i<=t;i++) { for(int j=n;j>=0;j--) { for(int k=M;k>=0;k--) { f[j][k]=min(f[j][k],f[max(j-v[i],0LL)][max(k-m[i],0LL)]+w[i]); } } } cout<<f[n][M]; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
完全背包
完全背包问题
题目链接:
U200606 完全背包问题 - 洛谷
思路:
设 f[i][j] 表示从前 i 个物品中选择,总体积不超过 j 的所有方案的最大值。
完全背包和 01 背包不同的地方就在于同一个物品它可以选不止一个。
状态转移:
如果不选这个物品那么 f[i][j]=f[i-1][j] 这是比较好想的。
如果要选的话 $f_{i,j}=max_{k=0}^{\infty}(f_{i-1,j-vk}+wk)$
f_{i,j}=max(f_{i-1,j},f_{i-1,j-v}+w,….,f_{i-1,j-kv}+wk)
f_{i,j-v}=max(f_{i-1,j-v},f_{i-1,j-2*v}+w,…,f_{i-1,j-(k-1)*v}+(k-1)*w)
$$
所以状态转移可以优化为 。
我们可以对比一下 01 背包和完全背包的状态转移公式:
1 2
| 01背包:f[i][j]=max(f[i-1][j],f[i-1][j-v]+w); 完全背包:f[i][j]=max(f[i-1][j],f[i][j-v]+w);
|
再用二维降一维的思路就得到最终的状态转移方程:f[j]=max(f[j],f[j-v]+w)。
和 01 背包不同的是这里不用逆序,因为 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 40 41 42 43 44 45 46
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' const int N=1010; int v,w; int f[N];
void solve2() { int n,m; cin>>m>>n; for(int i=1;i<=n;i++) { cin>>v>>w; for(int j=1;j<=m;j++) { if(j>=v)f[j]=max(f[j],f[j-v]+w); } } cout<<"max="<<f[m]; } signed main(){ IOS int t=1; while(t--) { solve2(); } }
|
买书
题目链接:
T171705 买书方案 - 洛谷
思路:
设 f[i] 为当前消费 i 的方案数。和数字组合很像。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const int N=1010; int f[N]; int a[5]={0,10,20,50,100}; void solve() { int n; cin>>n; f[0]=1; for(int i=1;i<=4;i++) { for(int j=a[i];j<=n;j++){ f[j]+=f[j-a[i]]; } } if(n==0)cout<<"0"; else cout<<f[n]; }
|
货币系统
题目链接:
P5020 [NOIP 2018 提高组] 货币系统 - 洛谷
思路:
考虑每个 a[i] 是否能被之前的数表示,如果可以那么就不需要这个数。所以遍历数组,如果当前数的被表示次数不为 1 则能够被其它数表示出来。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const int N=110; const int M=25005; int a[N]; int f[M]; void solve() { int n; cin>>n; memset(f,-0x3f,sizeof f); for(int i=1;i<=n;i++)cin>>a[i]; f[0]=0; int ans=0; for(int i=1;i<=n;i++) { for(int j=a[i];j<M;j++){ f[j]=max(f[j],f[j-a[i]]+1); } } for(int i=1;i<=n;i++){ if(f[a[i]]==1)ans++; } cout<<ans<<endl; }
|
多重背包
朴素多重背包:(适合在物品数量少的时候使用)
状态表示:f[i][j] 表示从前 i 个物品中选总体积不超过 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 25 26 27 28 29 30 31 32 33 34
| void solve(){ cin>>n>>m; int v,w,s; for(int i=1;i<=n;i++) { cin>>v>>w>>s; for(int j=0;j<=m;j++) { for(int k=0;k<=s&&k*v<=j;k++) { f[i][j]=max(f[i][j],f[i-1][j-k*v]+w*k); } } } cout<<f[n][m]; }
void solve(){ cin>>n>>m; int v,w,s; for(int i=1;i<=n;i++) { cin>>v>>w>>s; for(int j=m;j>=v;j--) { for(int k=0;k<=s&&k*v<=j;k++) { f[j]=max(f[j],f[j-k*v]+w*k); } } } cout<<f[m]; }
|
二进制优化:
当物品个数很多时,一个一个枚举并不合适。利用倍增思想和二进制,可以知道一个十进制数是可以转化为一个二进制比特串,进而可以表示为 和的形式。比如一个物品有 10 个那可以拆分成 ,这样可以拆分成三个箱子,一个有 8 个物品,一个有 2 个物品,转化为 01 背包的思路,只需要判断这个箱子选不选就可以了。
这里注意一下,我们相当于是吧每个物品都划分成了 +1个箱子,这样一共 N 个数最后数组需要开 ,所以 f 数组肯定不能开二维了。
拆分之后其实就是 cnt 个背包。之后运用 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
| int v[M],w[M]; int f[M]; void solve(){ int n,m; cin>>n>>m; int cnt=1; for(int i=1;i<=n;i++) { int a,b,s; cin>>a>>b>>s; for(int j=1;j<=s;j<<=1){ v[cnt]=j*a; w[cnt++]=j*b; s-=j; } if(s){ v[cnt]=s*a; w[cnt++]=s*b; } } n=cnt; for(int i=1;i<=n;i++) { for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]<<endl; }
|
单调队列:
题目链接:
P1886 【模板】单调队列 / 滑动窗口 - 洛谷
思路:
暴力做法就是以 f[i] 为以 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 25 26 27 28 29
| const int N=1e6+5; int a[N]; int q[N]; void solve() { int n,k; cin>>n>>k; int h=1,t=0; for(int i=1;i<=n;i++) { cin>>a[i]; } for(int i=1;i<=n;i++) { while(h<=t&&a[q[t]]>=a[i])t--; q[++t]=i; if(q[h]<i-k+1)h++; if(i>=k)cout<<a[q[h]]<<" "; } cout<<endl; h=1,t=0; for(int i=1;i<=n;i++) { while(h<=t&&a[q[t]]<=a[i])t--; q[++t]=i; if(q[h]<i-k+1)h++; if(i>=k)cout<<a[q[h]]<<" "; } }
|
连续子序列的最大和:
题目链接:
信息学奥赛一本通(C++版)在线评测系统
思路:
使用单调队列+前缀和数组,区间范围为 m,队列首元素为区间最小值,用区间内的前缀和减去最小值得到序列中的最大值。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>a[i]; s[i]=s[i-1]+a[i]; } int h=0,t=0,ans; q[0]=0,ans=s[1]; for(int i=1;i<=n;i++) { if(h<=t&&q[h]<i-m)h++; ans=max(ans,s[i]-s[q[h]]); while(h<=t&&s[q[t]]>=s[i])t--; q[++t]=i; } cout<<ans; }
|
单调队列优化:
通过在朴素版本添加输出观察可得,f 数组是按类更新的,可以把 f[0...m] 按体积 v 的余数划分成 v 个类。
f[j] 是由前面不超过 s 的同类值递推得到的,相当于从前面宽度为 s 的窗口挑选最大值来更新当前值。所以用单调队列来维护最大值。
代码:
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
| int f[M],g[M]; int q[M]; int v,w,s; void solve(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { memcpy(g,f,sizeof f); cin>>v>>w>>s; for(int j=0;j<v;j++) { int h=0,t=-1; for(int k=j;k<=m;k+=v) { if(h<=t&&q[h]<k-s*v)h++; if(h<=t)f[k]=max(g[k],g[q[h]]+(k-q[h])/v*w); while(h<=t&&g[k]>=g[q[t]]+(k-q[t])/v*w)t--; q[++t]=k; } } } cout<<f[m]; }
|
庆功会
题目链接:
U200694 庆功会 - 洛谷
思路:
多重背包模板题。
代码:
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 40 41
| const int N=6050; int f[N][N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { int v,w,s; cin>>v>>w>>s; for(int j=1;j<=m;j++) { for(int k=0;k<=s&&j>=k*v;k++) { f[i][j]=max(f[i][j],f[i-1][j-k*v]+w*k); } } } cout<<f[n][m]; }
const int N=6050; int f[N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { int v,w,s; cin>>v>>w>>s; for(int j=m;j>=v;j--) { for(int k=0;k<=s&&j>=k*v;k++) { f[j]=max(f[j],f[j-k*v]+w*k); } } } cout<<f[m]; }
|
分组背包
分组背包问题
题目链接:
U280369 分组背包问题 - 洛谷
思路:
用 f[i][j] 表示前 i 组物品放入容量为 j 的背包的方案集合的最大值。
对于第 i 组物品有以下 s+1 种方案:
1)一个都不选,f[i][j]=f[i-1][j]。
2)选第 i 组的第 1 个物品,f[i][j]=f[i-1][j-v[1]]+w[1]。
…
s+1)选第 i 组的第 k 个物品,f[i][j]=f[i-1][j-v[k]]+w[k]。
所以状态转移公式为 f[i][j]=max(f[i][j],f[i-1][j-v[k]]+w[k]) 。
代码:
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 40 41 42 43 44 45
| const int N=110; int s[N]; int v[N][N],w[N][N]; int f[N][N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>s[i]; for(int j=1;j<=s[i];j++)cin>>v[i][j]>>w[i][j]; } for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++) { for(int k=0;k<=s[i]&&j>=v[i][k];k++) { f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]); } } } cout<<f[n][m]; }
void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>s[i]; for(int j=1;j<=s[i];j++)cin>>v[i][j]>>w[i][j]; } for(int i=1;i<=n;i++) { for(int j=m;j>=0;j--) { for(int k=0;k<=s[i]&&j>=v[i][k];k++) f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); } } cout<<f[m]; }
|
机器分配
题目链接:
P2066 机器分配 - 洛谷
思路:
这个题目也可可以转换为分组背包问题,把每个公司看成一个分组。求具体方案类比于上面的 01 背包求具体方案,倒着枚举让最优解落到 f[1][m],同时记录记录 f[i][j] 取到最大值时,第 i 家公司占有的机器数量。
反向DP先决策后面的公司再决策前面的公司,当盈利相等时,保留前面公司(编号小)分配更少的方案。这里是 <。当使用 <= 时得到的答案就是编号小的公司尽量多分配。
代码:
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
| const int N=100; int f[N][N]; int w[N][N]; int way[N][N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++)cin>>w[i][j]; } for(int i=n;i;i--) { for(int j=1;j<=m;j++) { for(int k=0;k<=j;k++) { if(f[i][j]<f[i+1][j-k]+w[i][k]){ f[i][j]=f[i+1][j-k]+w[i][k]; way[i][j]=k; } } } } cout<<f[1][m]<<endl; for(int i=1,j=m;i<=n;i++) { cout<<i<<" "<<way[i][j]<<endl; j-=way[i][j]; } }
|
金明的预算方案
题目链接:
P1064 [NOIP 2006 提高组] 金明的预算方案 - 洛谷
思路:
本质也是分组背包,每个主件代表一个组,附件是组中的物品。这样一个主件最多两附件,所以最多四种选法(一主、一主一附(两种)、一主两附、),这里用到了二进制思想去优化,具体见代码注释。
代码:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' #define PII pair<int,int> #define x first #define y second const int N=2e5+5; PII a[N]; vector<PII> b[N]; int f[N]; void solve() { int n,m; cin>>m>>n; for(int i=1;i<=n;i++) { int v,p,q; cin>>v>>p>>q; p*=v; if(!q)a[i]={v,p}; else b[q].push_back({v,p}); } for(int i=1;i<=n;i++) { if(!a[i].x)continue; for(int u=m;u>=0;u--) { for(int j=0;j<1<<b[i].size();j++)
{ int v=a[i].x,w=a[i].y; for(int k=0;k<b[i].size();k++) { if(j>>k&1) { v+=b[i][k].x; w+=b[i][k].y; } } if(u>=v)f[u]=max(f[u],f[u-v]+w); } } } cout<<f[m]; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
混合背包
题目链接:
T302125 混合背包 - 洛谷
代码:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' #define PII pair<int,int> const int N=1010; int v[N],s[N],w[N]; int f[N]; void solve() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { cin>>v[i]>>w[i]>>s[i]; } for(int i=1;i<=n;i++) { if(!s[i]) { for(int j=v[i];j<=m;j++){ f[j]=max(f[j],f[j-v[i]]+w[i]); } } else{ if(s[i]==1){ for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } }else{ for(int k=1;k<=s[i];k<<=1) { for(int j=m;j>=v[i]*k;j--){ f[j]=max(f[j],f[j-k*v[i]]+k*w[i]); } s[i]-=k; } if(s[i]){ for(int j=m;j>=v[i]*s[i];j--){ f[j]=max(f[j],f[j-s[i]*v[i]]+w[i]*s[i]); } } } } } cout<<f[m]; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
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 40 41 42 43 44 45 46 47 48
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' #define PII pair<int,int> const int N=1010; int f[N]; int c[N*8],w[N*8]; int idx=1; void solve() { int n,m; cin>>n>>m; int v,W,s; for(int i=1;i<=n;i++) { cin>>v>>W>>s; if(s==0)s=m/v; for(int j=1;j<=s;j<<=1){ c[idx]=j*v; w[idx++]=j*W; s-=j; } if(s){ c[idx]=s*v; w[idx++]=s*W; } } for(int i=1;i<=idx;i++) { for(int j=m;j>=c[i];j--){ f[j]=max(f[j],f[j-c[i]]+w[i]); } } cout<<f[m]; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|
有依赖的背包问题
题目链接:
U436983 有依赖的背包问题 - 洛谷
代码:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <bits/stdc++.h> using namespace std; #define int long long #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); #define endl '\n' #define PII pair<int,int> int n,m,root; const int N=110; int h[N],e[N],ne[N],idx; int v[N],w[N]; int f[N][N]; void add(int a,int b){ e[idx]=b; ne[idx]=h[a]; h[a]=idx++; } void dfs(int u){ for(int i=h[u];i!=-1;i=ne[i]){ int s=e[i]; dfs(s); for(int j=m-v[u];j>=0;j--){ for(int k=0;k<=j;k++) { f[u][j]=max(f[u][j],f[u][j-k]+f[s][k]); } } } for(int j=m;j>=v[u];j--)f[u][j]=f[u][j-v[u]]+w[u]; for(int j=0;j<v[u];++j)f[u][j]=0; } void solve() { memset(h,-1,sizeof h); cin>>n>>m; for(int i=1;i<=n;i++) { int p; cin>>v[i]>>w[i]>>p; if(p==-1)root=i; else add(p,i); } dfs(root); cout<<f[root][m]; } signed main(){ IOS int t=1; while(t--) { solve(); } }
|