题目一 01背包
有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
分析 :
如果当前j大于v的情况下,可以得出状态转移方程为f[i,j]=max(f[i-1,j],f[i-1,j-v]+w)
否则,f[i,j]=f[i-1,j]
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N=1010;int v[N],w[N];
int f[N][N];
int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];for(int i=1;i<=n;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]);}}cout<<f[n][m]<<endl;return 0;
}
优化成一维:
观察代码,可以发现只用到了前一维的状态,所以我们可以去掉第一维
此时状态为,前n件物品中总价值不超过j的状态
我们去掉一维之后,注意j要逆序枚举,由于我们的状态转移方程是f[j]=max(f[j],f[j-v[i]]+w[i]);
如果不逆序枚举的话,f[j-v[i]]本来应该是第i-1轮的状态,但是会成为第i轮的状态,即污染。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N=1010;int f[N];
int w[N];
int v[N];int main()
{int n,m;cin>>n>>m;for(int i=1;i<=n;i++) cin>>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]);//cout<<"f["<<j<<"]:"<<f[j]<<endl;}cout<<f[m]<<endl;
}
题目二 完全背包
有 N种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
分析:
看分析,三重循环才能搞定
优化为二重循环
f[i-1,j-x*v]+x*w=max(f[i,j-v]+w)
如果 j 大于 v ,f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
否则 f[i][j]=f[i-1][j];
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N=10010;int v[N],w[N];
int n,m;
int f[N][N];int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i];for(int i=1;i<=n;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][j-v[i]]+w[i]);}cout<<f[n][m]<<endl;return 0;
}
优化为一维:
与01背包一样,都只用到了一维
但是完全背包表达式中,f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
用到了第i维的状态,所以正序枚举 j
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N=1010;
int f[N];
int v[N],w[N];int main()
{int n,m;cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i];for(int i=1;i<=n;i++)for(int j=v[i];j<=m;j++){f[j]=max(f[j],f[j-v[i]]+w[i]);}cout<<f[m]<<endl;return 0;}
题目三 多重背包I
有 N种物品和一个容量是 V 的背包。
第 i 种物品最多有 si件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
分析:
三重循环,直接得出结果即可
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;
const int N=110;
int f[N][N];
int v[N],w[N],s[N];int main()
{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++)for(int j=0;j<=m;j++)for(int k=0;k<=s[i]&&k*v[i]<=j;k++){f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);}cout<<f[n][m]<<endl;return 0;
}
优化成一维:
和01背包优化方式一样
j要倒序枚举
代码:
#include <iostream>
#include <algorithm>
#include <cstring>using namespace std;const int N=110;int v,w,s;
int f[N];int main()
{int n,m;cin>>n>>m;for(int i=1;i<=n;i++){cin>>v>>w>>s;for(int j=m;j>=1;j--)//看作01背包,从后往前枚举for(int k=0;k<=s&&k*v<=j;k++){f[j]=max(f[j],f[j-k*v]+k*w);}}cout<<f[m]<<endl;return 0;
}
题目四 多重背包II
有 N 种物品和一个容量是 V的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
分析:
本题与前面的区别:数据范围变大
不能把一件物品拆成s个当作01背包来解
可以利用二进制的办法把一件物品分成logs件物品,然后当作01背包解即可
如果s=10,那么可以拆成1 2 4 3 ,注意最后一个要10-1-2-4得到
这样得到的4件物品科比表示0-10这10件物品
所以本题用到的算法是二进制优化
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>using namespace std;const int N=2010;
int f[N];int n,m;
int v,w,s;struct goods{int v,w;
};int main()
{cin>>n>>m;vector<goods> good;for(int i=1;i<=n;i++){cin>>v>>w>>s;for(int k=1;k<=s;k*=2){s-=k;good.push_back({v*k,w*k});}if(s>0) good.push_back({v*s,w*s});}for(auto g:good){for(int j=m;j>=g.v;j--){f[j]=max(f[j],f[j-g.v]+g.w);}}cout<<f[m]<<endl;return 0;
}
题目五 多重背包III
有 N种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V (0<N≤1000,0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
提示
本题考查多重背包的单调队列优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
分析:
本题数据范围更加庞大,单单用二进制优化显然不行
根据题意
f(i,j)=max(f(i−1,j),f(i−1,j−v)+w,⋯,f(i−1,j−sv)+sw)
f(i,j−v)=max(f(i−1,j−v),f(i−1,j−2v)+w,⋯,f(i−1,j−(s+1)v)+sw)
f(i,j−2v)=max(f(i−1,j−2v),f(i−1,j−3v)+w,⋯,f(i−1,j−(s+2)v)+sw)
⋯
f(i,r+sv)=max(f(i−1,r+sv),f(i−1,r+(s−1)v)+w,⋯,f(i−1,r)+sw)
f(i,r+(s−1)v)=max(f(i−1,r+(s−1)v),⋯,f(i−1,r)+(s−1)w)
⋯
f(i,r+2v)=max(f(i−1,r+2v),f(i−1,r+v)+w,f(i−1,r)+2w)
f(i,r+v)=max(f(i−1,r+v),f(i−1,r)+w)
f(i,r)=f(i−1,r)
其中 r=j mod vi
去掉i-1和w
f(i,r)=fr
f(i,r+v)=max(fr+v,fr)
f(i,r+2v)=max(fr+2v,fr+v,fr)
⋯
f(i,r+(s−1)v)=max(fr+(s−1)v,fr+(s−2)v,⋯,fr+(s−1))
f(i,r+sv)=max(fr+sv,fr+(s−1)v,⋯,fr) (滑动窗口已满)
f(i,r+(s+1)v)=max(fr+(s+1)v,fr+sv,⋯,fr+v) (滑动窗口已满)
⋯
f(i,j−2v)=max(fj−2v,fj−3v,⋯,fj−(s+2)v) (滑动窗口已满)
f(i,j−v)=max(fj−v,fj−2v,⋯,fj−(s+1)v) (滑动窗口已满)
f(i,j)=max(fj,fj−v,⋯,fj−sv) (滑动窗口已满)
观察可发现,我们要求的始终是一个滑动窗口的最大值
那么我们利用滑动窗口来进行优化
第一重循环枚举每个物品
第二重循环确定有几个滑动窗口,由于一个滑动窗口中都是fj−k*v,所以每个滑动窗口对v的模值都相同
第三重循环枚举每个滑动窗口,求每个滑动窗口中的最大值
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N=1010,M=20010;
int f[N][M];
int q[M];
int v[M],w[M],s[M];int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];for(int i=1;i<=n;i++){for(int r=0;r<v[i];r++){int hh=0,tt=-1;for(int j=r;j<=m;j+=v[i]){if(hh<=tt&&q[hh]<j-s[i]*v[i]) hh++;while(hh<=tt&&f[i-1][q[tt]]+(j-q[tt])/v[i]*w[i]<=f[i-1][j]) tt--;q[++tt]=j;f[i][j]=f[i-1][q[hh]]+(j-q[hh])/v[i]*w[i];}}}cout<<f[n][m]<<endl;
}
观察代码发现只用到了前i-1维的值,所以同样可以优化为一维,需要备份一下
优化为一维:
#include <iostream>
#include <algorithm>
#include <cstring>using namespace std;const int N=1010,M=20010;int f[M],g[M];
int v[M],w[M],s[M];
int q[M];
int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];for(int i=1;i<=n;i++){memcpy(g,f,sizeof f);for(int r=0;r<v[i];r++){int hh=0,tt=-1;for(int j=r;j<=m;j+=v[i]){if(hh<=tt&&q[hh]<j-s[i]*v[i]) hh++;while(hh<=tt&&g[q[tt]]+(j-q[tt])/v[i]*w[i]<=g[j]) tt--;q[++tt]=j;f[j]=g[q[hh]]+(j-q[hh])/v[i]*w[i];}}}cout<<f[m]<<endl;
}
附:滑动窗口模版题
给定一个大小为 n≤106的数组。
有一个大小为 k的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7]
,k 为 33。
窗口位置 | 最小值 | 最大值 |
---|---|---|
[1 3 -1] -3 5 3 6 7 | -1 | 3 |
1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
1 3 -1 -3 5 [3 6 7] | 3 | 7 |
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式
输入包含两行。
第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
第二行有 n 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
8 3
1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
分析:
使用单调队列来求每个窗口中的最大值和最小值
朴素做法:每次遍历一遍窗口中的所有值,求出对应的最大值和最小值
优化:求最大值时,如果即将入队的元素大于队尾元素,队尾元素删去,使得每次入队的元素都是单调递增的,从而每次输出队头即得到最大值
滑动窗口的算法步骤是
首先判断队头是否滑出队列
实现每次入队元素递增或者递减
如果遍历到的元素大于滑动窗口大小,输出队头即可
代码:
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N=1e6+5;int n,k;
int q[N];
int a[N];int main()
{cin>>n>>k;for(int i=0;i<n;i++) cin>>a[i];int hh=0,tt=-1;for(int i=0;i<n;i++){//判断队头是否已经滑出窗口if(hh<=tt&&q[hh]<i-k+1) hh++;while(hh<=tt&&a[q[tt]]>a[i]) tt--;q[++tt]=i;if(i-k+1>=0) cout<<a[q[hh]]<<' ';}puts("");hh=0,tt=-1;for(int i=0;i<n;i++){//判断队头是否已经滑出窗口if(hh<=tt&&q[hh]<i-k+1) hh++;while(hh<=tt&&a[q[tt]]<a[i]) tt--;q[++tt]=i;if(i-k+1>=0) cout<<a[q[hh]]<<' ';}puts("");
}