习题课3-1
数字三角形
-
一个三角形(金字塔状)有n行,第i行有i个数字,数字错位排列
-
求出从顶端走到底端的一条路径,满足路径上的数字之和最大,同时在路径上行走时,每次只能走到下一行相邻的数字上
-
4 1 2 3 4 5 6 7 8 9 10
-
输出20
-
每一次都要向下走一行,同时只能走到相邻两个点上,不会走回头路
-
每行只会经过一个点
-
在三角形某个位置(i,j)上,可以发现只有两个点可以走进该点,即(i-1,j),(i-1,j-1)
解法1
-
写一个搜索,从顶端向下找出所有到达底端的路径
-
会有大量重复
解法2
-
a(i,j)表示位置(i,j)上的数字,d(i,j)能得到的最优路径之和
-
某一个点的d(i,j),只能是d(i-1,j)和d(i-1,j-1)走过来,找出其大者,同时加上当前点a(i,j),即可写出状态转移方程
-
d ( i , j ) = m a x { d ( i − 1 , j − 1 ) , d ( i − 1 , j ) } + a ( i , j ) d(i,j) = max\{d(i-1,j-1),d(i-1,j)\} +a(i,j) d(i,j)=max{d(i−1,j−1),d(i−1,j)}+a(i,j)
-
i-1 j-1有可能越界,在外面包一层0,取max的时候自动就会取里面的数字非0的数字
-
最后答案就是:
-
m a x { d ( n , j ) ∣ 1 ≤ i ≤ n } max\{d(n,j)|1\leq i\leq n\} max{d(n,j)∣1≤i≤n}
-
即,动态规划
-
不会走到重复的地方->无后效性->有向无环图
-
每次只有两个决策->选一个最优的决策->全局最优
-
最后在底端一行里面比较出最大者
-
时间复杂度,每一次取了3次常数O(3) = O(1),状态树一共有n的平方个状态,所以就是O(n2)
背包问题1
- n种物品,每种物品有相应的价值和体积,物品分为两类,一类是单个物品,即该物品只有一个,一类是多个物品,即该物品有无限个
- 给定一个体积为V的背包,求一种装填方案使得价值之和最大
问题分析
- 单个物品用经典01背包
- 无限物品,即完全背包
解法1
01背包
-
令f(i,j)表示前i个物品,用一个容量为j的背包能装下物品(不能超过容量j,最大等于j)的最大价值之和
-
体积为v,价值为w的物品i,然后对于容量为j的背包
-
f ( i , j ) = m a x { f ( i − 1 , j ) , f ( i − 1 , j − v ) + w } f(i,j) = max \{f(i-1,j),f(i-1,j-v)+w\} f(i,j)=max{f(i−1,j),f(i−1,j−v)+w}
-
对于某个物品,两个策略就是放与不放
-
不放就是f(i-1,j),放的话就是f(i-1,j-v)+w
完全背包
-
类似于01背包
-
f ( i , j ) = m a x { f ( i − 1 , j ) , f ( i , j − v ) + w } f(i,j) = max \{f(i-1,j),f(i,j-v)+w\} f(i,j)=max{f(i−1,j),f(i,j−v)+w}
-
f(i,j-v):前i个物品,容量为j-v的背包,也就是说我们可能已经在这个j-v的背包里放过一些物品i了,这就包含了无限物品的意思。
-
更详细的解释:将无限个物品拆成一个一个的,然后做01背包,假如第a个物品到第b个物品都是我们拆的物品(完全相同的而且足够多),那么根据01背包,我们有:
-
KaTeX parse error: Undefined control sequence: \ at position 54: …},a\leq i\leq b\̲ ̲
-
我们将f(a…)、f(a+1…)、…、f(b,…)这些数组取最大值和到一起
滚动数组
-
01背包更新时只用到了<i的值,所以可以重复利用信息
-
01背包可以这些写(倒序枚举j)
-
f ( i , j ) = m a x { f ( j ) , f ( j − v ) + w } f(i,j) = max \{f(j),f(j-v)+w\} f(i,j)=max{f(j),f(j−v)+w}
-
倒序枚举计算时,在枚举物品i时,在倒序计算时,体积j时从后往前更新最大价值,此时后面的值等于前面的值加上物品i的价值,而前面的最大价值没有考虑加入物品i的情况,所以只考虑加入一次的情况
-
完全背包可以这样写(顺序枚举j)
-
f ( i , j ) = m a x { f ( j ) , f ( j − v ) + w } f(i,j) = max \{f(j),f(j-v)+w\} f(i,j)=max{f(j),f(j−v)+w}
-
顺序枚举时,在枚举物品i是,背包体积是从前往后更新最大价值,此时后面的值更新时,前面的最大价值计算已经考虑了加入1,2,…,若干件i的情况,此时就考虑了这个背包装满时最大能装物品i的情况,也就是无穷物品
-
得到的结果和二维结果一样
public class beibaowenti1_3_1 {
public static void main(String[] args) {
InputStream inputStream = System.in;
OutputStream outputStream = System.out;
InputReader in = new InputReader(inputStream);
PrintWriter out = new PrintWriter(outputStream);
Task solver = new Task();
solver.solve(in, out);
out.close();
}
static class Task {
// ================= 代码实现开始 =================
/* 请在这里定义你需要的全局变量 */
final int N = 5005;
int[] f = new int [N];
// n:物品个数
// V:背包的体积
// t:长度为n的数组,第i个元素若为0,表示物品i为单个物品;若为1,表示物品i为多个物品。(i下标从0开始,下面同理)
// w:长度为n的数组,第i个元素表示第i个物品的价值
// v:长度为n的数组,第i个元素表示第i个物品的体积
// 返回值:最大价值之和
int getAnswer(int n, int V, List<Integer> t, List<Integer> w, List<Integer> v) {
/* 请在这里设计你的算法 */
for (int i = 0; i < n; i++) {
if (t.get(i) == 0) {
// 01背包
// 倒序计算不具备累加性,每种物品只算了一次
// f[5] = max(f[5],f[5-v(0)]+w(0))对于体积为5的背包,只算了装一次序号0物品的情况
for (int j = V; j >=v.get(i) ; j--) {
f[j] = Math.max(f[j],f[j-v.get(i)]+w.get(i));
}
}else {
// 完全背包
// 正序计算具备累加性,物品可以算任意次,只要不超过背包体积
// f[v(0)] = max(f[v(0)],f[v(0)-v(0)]+w(0))
// f[v(0)+v(0)] = max(f[v(0)+v(0)],f[v(0)]+w(0))
for (int j = v.get(i); j <=V ; j++) {
f[j] = Math.max(f[j],f[j-v.get(i)]+w.get(i));
}
}
}
return f[V];
}
// ================= 代码实现结束 =================
void solve(InputReader in, PrintWriter out) {
int n = in.nextInt(), V = in.nextInt();
List<Integer> T = new ArrayList<>();
List<Integer> W = new ArrayList<>();
List<Integer> _V = new ArrayList<>();
for (int i = 0; i < n; ++i) {
int t = in.nextInt(), w = in.nextInt(), v = in.nextInt();
T.add(t);
W.add(w);
_V.add(v);
}
out.println(getAnswer(n, V, T, W, _V));
}
}
拓展
- 01背包,选与不选
- 完全背包,每个物品有无限个,想选多少选多少
- 多重背包,物品不是有限个,但是有多个,同样是想选多少选多少
背包问题2
- n个物品,每个物品有一个体积和价值
- q次询问,若把物品x丢弃,剩下的物品装进大小为V的背包得到的最大价值是多少?
问题分析
-
把剩下的物品直接拿来做01背包,可以通过一部分数据
-
把容量为V的背包,拆分成两个背包,体积分别是V1、V2(V1+V2=V)
-
假若我们将一堆物品分成了两份,然后分别将每一份撞到拆出来的背包里,同时求出最优解,那么
-
A n s ( V ) = m a x { A n s 1 ( V 1 ) + A n s 2 ( V 2 ) ∣ V 1 + V 2 = V , V 1 , V 2 > = 0 } Ans(V) = max\{Ans1(V1)+Ans2(V2)|V1+V2=V,V1,V2>=0\} Ans(V)=max{Ans1(V1)+Ans2(V2)∣V1+V2=V,V1,V2>=0}
-
对应到题目,如果丢弃的是x,x序号之前的物品用v1背包装,x序号之后的物品用v2背包装
public class beibaowenti2_3_1 {
public static void main(String[] args) {
InputStream inputStream = System.in;
OutputStream outputStream = System.out;
InputReader in = new InputReader(inputStream);
PrintWriter out = new PrintWriter(outputStream);
Task solver = new Task();
solver.solve(in, out);
out.close();
}
static class Task {
// ================= 代码实现开始 =================
final int N = 5005;
int[][] d = new int[N][N];
int[][] f = new int[N][N];
/* 请在这里定义你需要的全局变量 */
// n个物品,每个物品有体积价值,求若扔掉一个物品后装进给定容量的背包的最大价值
// n:如题
// w:长度为n+1的数组,w.get(i)表示第i个物品的价值(下标从1开始,下标0是一个数字-1,下面同理)
// v:长度为n+1的数组,v.get(i)表示第i个物品的体积
// q:如题
// qV:长度为q+1的数组,qV.get(i)表示第i次询问所给出的背包体积
// qx:长度为q+1的数组,qx.get(i)表示第i次询问所给出的物品编号
// 返回值:返回一个长度为q的数组,依次代表相应询问的答案
List<Integer> getAnswer(int n, List<Integer> w, List<Integer> v, int q, List<Integer> qV, List<Integer> qx) {
/* 请在这里设计你的算法 */
// 丢弃的x之前的最大价值背包
// 枚举n个物品
// 计算前缀背包
for (int i = 1; i <= n; i++) {
// 由于是二维数组,需要做充填备忘
// 对于i=1时,d[1][j] = d[0][j]
// 对于i=2时,d[2][j] = d[1][j]
for (int j = 0; j < v.get(i); j++) {
d[i][j] = d[i-1][j];
}
// 01背包问题
// 对于体积为v,价值为w的物品i,要装填到背包体积为j的背包中的最大价值
// d[i-1][j]表示不放物品i,只放i之前的物品时所能装的最大价值
// d[i-1][j-v.get(i)]+w.get(i) 表示放入物品i后,恰好装满背包,此时由没放物品i,背包体积为当前体积减去物品i体积的最大价值背包加上物品i的价值
// 两者中较大者即为最大价值
for (int j = v.get(i); j <= 5000; j++) {
d[i][j] = Math.max(d[i-1][j],d[i-1][j-v.get(i)]+w.get(i));
}
}
// 丢弃的x之后的最大价值背包
// 与上面的过程相反,上面是依照物品正序求最大背包
// 此处依照物品顺序倒序求最大背包
// 计算后缀背包
for (int i = n; i >= 1; i--) {
for (int j = 0; j < v.get(i); j++) {
f[i][j] = f[i+1][j];
}
for (int j = v.get(i); j <= 5000; j++) {
f[i][j] = Math.max(f[i+1][j],f[i+1][j-v.get(i)]+w.get(i));
}
}
List<Integer> ans = new ArrayList<>();
for (int k = 1; k <= q; k++) {
int x = qx.get(k),V = qV.get(k);
// 最大值,将背包拆分成x之前的背包,和x之后的背包,所有组合中的背包和
int mx = 0;
for (int i = 0; i <= V; i++) {
mx = Math.max(mx,d[x-1][i]+f[x+1][V-i]);
}
ans.add(mx);
}
return ans;
}
// ================= 代码实现结束 =================
void solve(InputReader in, PrintWriter out) {
int n, q;
List<Integer> v = new ArrayList<>();
List<Integer> w = new ArrayList<>();
List<Integer> qv = new ArrayList<>();
List<Integer> qx = new ArrayList<>();
v.add(-1);
w.add(-1);
qv.add(-1);
qx.add(-1);
n = in.nextInt();
for (int i = 0; i < n; ++i) {
int a = in.nextInt(), b = in.nextInt();
v.add(a);
w.add(b);
}
q = in.nextInt();
for (int i = 0; i < q; ++i) {
int a = in.nextInt(), b = in.nextInt();
qv.add(a);
qx.add(b);
}
List<Integer> ans = getAnswer(n, w, v, q, qv, qx);
for (int i = 0; i < q; ++i)
out.println(ans.get(i));
}
}