入門動態規劃問題

hihocoder這周欠了三題,因而今天一波結束了。而後發現這三個題目彷佛都很簡單,而且仍是一類問題裏面的。全部就寫成一次的吧。java

動態規劃問題,提及來,理論上是每一個搞ACM的人都會學的,並且應該是最開始就學的。由於動態規劃問題是各類各樣比賽的寵兒啊,幾乎每次比賽必出動態規劃。樓教主的「男人八題」裏面就有幾個動態規劃問題,是須要結合數據結構和動態規劃才能解決的問題。不過不在此次範圍內。git


固然,在寫動態規劃問題以前,顯然是要推薦一波《揹包九講》的,畢竟寫的很好。傳送門github


1)數字三角形問題數據結構

數字三角形問題其實本質上也就是選和不選問題。學習

就以hihocoder-1037-數字三角形這個題目來講吧。優化

    2
   6 4
  1 2 8
 4 0 9 6
6 5 5 3 6
這個三角形,從最上面走到下面,每次只能向左下或者右下走,問最後的路徑上的數的和最大爲多少。

若是單純的採用貪心的策略走的話,2-6-2-9-5,因而最大路徑變成了24,然而結果倒是28,是2-4-8-9-5ui

顯然在第一步走錯了。spa

那咱們試試用搜索的方法,搜索由於採用遞歸的方式,因此其實把每一種方案都選擇了一下。code

2-6-1-4-6blog

2-6-1-4-5

....

2-4-8-9-5

....

2-4-8-6-6

顯然用搜索時可以找到最後的結果的。

可是每種方案都找出來了。總共有2^4種方案,因此對於任意的n,有2^(n-1)種方案,那麼若是n是100,顯然要找到2^99種方案,這樣的效率,是很是可怕的。

那咱們再考慮下,發現,在搜索的時候,不少步驟是重複的。好比在2-6-2和2-4-2後面的幾種方案,雖然都是同樣的結果,可是卻由於前面不一樣因此被重複計算了。這是致使效率低的緣由。那該怎麼解決呢?

很簡單,把後面運行的結果保存一下,每次遇到相同的直接用不就能夠了麼。

因此假設f[i][j]表示從底部走到(i,j)這個位置的路徑上所通過的最大路徑和。這個狀態咱們發現是能夠轉移的。

f[i+1][j]的狀態只須要向上面走一個位置,就能夠轉移到f[i][j],同理f[i+1][j+1]也是這樣

而後就能夠獲得一個方程f[i][j] = max(f[i+1][j], f[i+1][j+1]) + val[i][j];

獲得這個方程就能夠輕鬆的解決這個問題了。

因此動態規劃的核心思想,其實就是狀態和狀態轉移方程。

附上hihocoder-1037-數字三角形的ac代碼:

import java.util.Scanner;
import java.io.BufferedInputStream;

public class Main {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
		int n = in.nextInt();
		int[][] a = new int[n][n];
		int[][] dp = new int[n][n];
		for( int i = 0; i < n; ++i )
			for( int j = 0; j <= i; ++j )
				a[i][j] = in.nextInt();
		
		for( int i = 0; i < n; ++i ) dp[n - 1][i] = a[n - 1][i];
		for( int i = n - 2; i >= 0; --i )
			for( int j = i; j >= 0; --j )
				dp[i][j] = Math.max(  dp[i + 1][j], dp[i + 1][j + 1] ) + a[i][j];
		System.out.println( dp[0][0] );
	}
}



2)01揹包

這個揹包問題就是選和不選的問題,從這個揹包問題能衍生出不少問題,好比POJ-2184這個題目就是一個很好的01揹包變形。

不過今天是基礎的揹包,那我仍是說基礎的揹包問題吧。

就以hihocoder-1038-01揹包這個題目來講吧。

有n個獎品,m個獎卷。第i個獎品兌換須要w[i]個獎卷,這個獎品由v[i]的價值。問使用m個獎卷能換到的獎品價值最大爲多少。

其實也是一個選和不選的問題了,與上面那個三角形仍是很是相似的。

看到這個問題的時候就會有一種想法,就是強行搜索一波,把選和不選每一個物品的兩種狀況都給搜索出來,這種不失爲一種辦法,可是確實很麻煩效率很低,即便物品只有30個也會一波GG,固然若是某些題目剪枝剪得很是棒那是另一回事了。

根據咱們作上面那個三角形的經驗,咱們要找到一個狀態,一個狀態轉移方程就行了。

那這個狀態怎麼找呢?

f[i][j]表示當裝了第i個物品,而且花了j個獎卷以後所能得到的最大價值。

這樣就成功的找出來了。你要是問我這是怎麼找到的。我也只能說一句無可奉告,畢竟我也只是學習了這些以後才知道的。

因此狀態轉移方程就是f[i][j] = max(f[i - 1][j - w[i]] + v[i], f[i][j]);(可優化)

因此直接給出hihocoder-1038-01揹包的代碼。

import java.util.Scanner;
import java.io.BufferedInputStream;

public class Main {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
		int n = in.nextInt();
		int m = in.nextInt();
		int[] a = new int[n];
		int[] b = new int[n];
		for( int i = 0; i < n; ++i ){
			a[i] = in.nextInt();
			b[i] = in.nextInt();
		}
		int[] dp = new int[m + 10];
		for( int i = 0; i < n; ++i ) {
			for( int j = m; j >= a[i]; --j ) {
				if( j >= a[i] ) 
					dp[j] = Math.max(dp[j - a[i]] + b[i], dp[j]);
			}
		}
		System.out.println( dp[m] );
	}
}

3)徹底揹包

其實在說徹底揹包以前,說下多重揹包比較好。不過想一想我這麼懶,仍是算了吧。

hihocoder-1043-徹底揹包爲例。

首先能獲取的是無限的,每種獎品能被無限次獲取。看到這裏心裏一顫啊,竟然無限次獲取,那怎麼搞啊。然而,雖然獎品是無限次獲取的,可是手中的獎卷倒是有限的。對於每種物品,能得到的物品數,也不過就是m / w[i]而已啊。

因此所以,就成功的把徹底揹包轉換成了多重揹包。多重揹包的解法不少,好比再轉換成01揹包去計算,或者利用二進制來優化多重揹包。

即,把多重揹包最多能選的次數w,變成[2^0, 2^1, 2^2, 2^3,..., 2^k, w - 2^k]

這樣的效率比一次一次的找要高的多了。

直接附hihocoder-1043-徹底揹包的ac代碼:

import java.util.Scanner;
import java.io.BufferedInputStream;

public class Main {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
		int n = in.nextInt();
		int m = in.nextInt();
		int[] a = new int[n];
		int[] b = new int[n];
		int[] c = new int[n];
		for( int i = 0; i < n; ++i ){
			a[i] = in.nextInt();
			b[i] = in.nextInt();
			c[i] = m / a[i];
		}
		int[] dp = new int[m + 10];
		for( int i = 0; i < n; ++i ) {
			int t = c[i], r = 1;
			while( t > 0 ) {
				if( r > t ) r = t;
				t -= r;
				
				for( int j = m; j >= r * a[i]; --j ) {
					dp[j] = Math.max( dp[ j - r * a[i] ] + r * b[i], dp[j]);
				}
				r <<= 1;
			}
		}
		System.out.println( dp[m] );
	}
}
相關文章
相關標籤/搜索