oj地址:01揹包。
首先說一下這道題引發的思考,我習慣把狀態轉移方程稱作小單元,它一般是以一個單元格爲計算單位的遞推過程,然後在小單元之上,我又把行與行之間的關係,稱作大單元。很多動態規劃的題,一個單元格一般依賴於其他幾個單元格,這是從單元格的角度上去看問題,如果從行的角度去看問題,很多的都是一行上的數據,只會依賴固定的一行或者幾行,這裏就是空間優化的出發點。比如一個二維表的空間是O(mn)的,而發現其第i行永遠只依賴第i-1行,那麼就可以只給它分配兩行,然後循環使用。
1.最初始的解決方法。
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int C=sc.nextInt();
int N=sc.nextInt();
int[] V=new int[N+1];//評價
int[] P=new int[N+1];//價格
int[][] dp=new int[N+1][C+1];//表示dp[i][j]表示前i件放入容量爲j的揹包時的最大評價
for(int i=1;i<=N;i++){
P[i]=sc.nextInt();
V[i]=sc.nextInt();
}
//第一行都爲0,java裏省去初始化
for(int i=1;i<=N;i++){
for(int j=0;j<=C;j++){
if(j-P[i]>=0){
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-P[i]]+V[i]);
}else{
dp[i][j]=dp[i-1][j];
}
}
}
System.out.println(dp[N][C]);
}
}
}
2.在1的基礎上,發現在動態規劃的過程中,永遠是一行和上一行之間的依賴關係。那麼給它分配兩行,循環使用就行了。
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int C=sc.nextInt();
int N=sc.nextInt();
int[] V=new int[N+1];//評價
int[] P=new int[N+1];//價格
int[][] dp=new int[2][C+1];//這次只分配兩行給它
for(int i=1;i<=N;i++){
P[i]=sc.nextInt();
V[i]=sc.nextInt();
}
//第一行都爲0,java裏省去初始化
for(int i=1;i<=N;i++){
for(int j=0;j<=C;j++){
//來回倒騰,不要浪費
dp[i%2][j] = dp[(i-1)%2][j];
if(j-P[i]>=0){
dp[i%2][j]=Math.max(dp[(i-1)%2][j],dp[(i-1)%2][j-P[i]]+V[i]);
}else{
dp[i%2][j]=dp[(i-1)%2][j];
}
}
}
System.out.println(dp[N%2][C]);
}
}
}
3.在2的基礎上,對於一個小單元來說,它永遠只需要自己上方,和自己左方的元素,右邊的不需要。如果把兩行的空間優化成一行。計算當前行,從右往左修改,修改過的值,就是已經固定了的當前行的所求值,還待修改的,就是上一行的遺留值(供後面的使用),當前行和上一行在一行裏同時體現,而且互相不影響,修改的過程中,上一行逐漸變成這一行。
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int C=sc.nextInt();
int N=sc.nextInt();
int[] V=new int[N+1];//評價
int[] P=new int[N+1];//價格
int[] dp=new int[C+1];//一行的話,直接一維數組
for(int i=1;i<=N;i++){
P[i]=sc.nextInt();
V[i]=sc.nextInt();
}
//第一行都爲0,java裏省去初始化
for(int i=1;i<=N;i++){
for(int j=C;j>=0;j--){
if(j-P[i]>=0){
//注意這裏的細節,左邊的dp[j],表示正在修改的當前行,右邊的dp[j]表示上一行,雖然都是dp[j],但是意義不同
dp[j]=Math.max(dp[j],dp[j-P[i]]+V[i]);
}
/**
看不懂的話,加上這句就看懂了
else{
dp[j]=dp[j];
}
*/
}
}
System.out.println(dp[C]);
}
}
}
4.常數級優化1:單純的coding優化。二層for循環的邏輯,這樣寫是一樣的。
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int C=sc.nextInt();
int N=sc.nextInt();
int[] V=new int[N+1];//評價
int[] P=new int[N+1];//價格
int[] dp=new int[C+1];//表示dp[i][j]表示前i件放入容量爲j的揹包時的最大評價
for(int i=1;i<=N;i++){
P[i]=sc.nextInt();
V[i]=sc.nextInt();
}
//第一行都爲0,java裏省去初始化
for(int i=1;i<=N;i++){
for(int j=C;j>=P[i];j--){
dp[j]=Math.max(dp[j],dp[j-P[i]]+V[i]);
}
}
System.out.println(dp[C]);
}
}
}
5.常數級優化2:揹包九講裏的常數級優化,優化下限公式如圖,但是我感覺會增加計算或者增加空間。沒去實現。