算法基礎知識——動態規劃

算法基礎知識——動態規劃

目錄:

  1. 基礎知識
    1. 分治法和動態規劃的區別
    2. 動態規劃算法設計步驟
    3. 最優子結構性質定義
    4. 動態規劃兩種等價的實現方法(自頂向下帶備忘、自底向上)
    5. 子問題圖
  2. 經典問題
    1. 鋼條切割
    2. 矩陣鏈乘法
    3. 最大連續子序列和
    4. 最長遞增子序列
    5. 最長公共子序列
    6. 0-1揹包問題
    7. 完全揹包問題
  3. 應用實例
    1. N階樓梯上樓問題【華中科技大學】
    2. 吃糖果【北京大學】
    3. 最大序列和【清華大學】
    4. 最大子矩陣【北京大學】
    5. 最大連續子序列【浙江大學】
    6. 攔截導彈【北京大學】
    7. 最大上升子序列和【北京大學】
    8. 方塊塗色【暨南大學】
    9. 合唱隊形【北京大學】
    10. Common Subsequence【Southeastern Europe 2003】
    11. Coincidence【上海交通大學】
    12. 點菜問題【北京大學】
    13. 採藥【北京大學】
    14. 最小郵票數【清華大學】
    15. Piggy-Bank【Central Europe 1999】
    16. 悼念512汶川大地震遇難同胞——珍惜現在,感恩生活【2008-06-18《 ACM程序設計》期末考試——四川加油!中國加油!】
    17. The Triangle【POJ 1163】
    18. 放蘋果【北京大學】
    19. 整數拆分【清華大學】
    20. Monkey Banana Problem【light oj 1004】

一、基礎知識

1、分治法和動態規劃的區別:

  • 分治法(Divide and Conquer):
    • 將問題劃分爲互不相交的子問題,遞歸地求解子問題,再將它們的解組合起來,求出原問題的解。
    • 反覆求解公共子子問題。
  • 動態規劃(Dynamic programming):
    • 應用於子問題重疊的情況,即不同的子問題具有公共的子子問題。
    • 對每個子子問題只求解一次,將其保存在一個表格中,從而無需每次求解一個子子問題時都重新計算。
    • 通常用於求解最優化問題(Optimization problem),找到具有最優值的解,即找到問題的一個最優解(an optimal solution),而不是最優解(the optimal solution),因爲可能有多個解都達到最優值。

2、動態規劃算法設計步驟:

  • 刻畫一個最優解的結構特徵;
  • 遞歸地定義最優解的值;
  • 計算最優解的值,通常採用自底向上的辦法;
  • 利用計算出的信息構造一個最優解。

3、最優子結構性質(optimal substructure):

  • 問題的最優解由相關子問題的最優解組合而成,而這些子問題可以獨立求解。

4、動態規劃方法是付出額外的內存空間來節省計算時間,是時空權衡(time-memory trade-off)的例子,可以將一個指數時間的解轉化爲一個多項式時間的解。

5、動態規劃兩種等價的實現方法:

  • 帶備忘機制的自頂向下法(top-down with memoization):
    • 按自然的遞歸形式編寫過程,但過程會保存每個子問題的解(通常保存在一個數組或散列表中)。
    • 當需要一個子問題的解時,過程首先檢查是否已經保存過此解。如果是,則直接返回保存的值;否則,按通常方式計算這個子問題。
  • 自底向上法(bottom-up method):
    • 需要恰當定義子問題“規模”的概念,使得任何子問題的求解都只依賴於“更小的”子問題的求解。因而可以將子問題按規模排序,按由小到大的順序進行求解。
    • 當求解某個子問題時,它所依賴的那些更小的子問題都已求解完畢,結果已經保存。每個子問題只求解一次,當我們求解它(也是第一次遇到它)時,它的所有前提子問題都已求解完成。
  • 區別:兩種方法具有相同的漸近運行時間。在某些特殊情況下,自頂向下方法並未真正遞歸地考察所有可能的子問題。由於沒有頻繁的遞歸調用的開銷,自底向上方法的時間複雜性函數通常具有更小的係數。

6、子問題圖:

  • 定義:一個有向圖,每個頂點唯一地對應一個子問題,若求解子問題x的最優解時需要直接用到子問題y的最優解,那麼在子問題圖中就會有一條從子問題x的頂點到子問題y的頂點的有向邊。
  • 特點:
    • 自底向上動態規劃算法是按逆拓撲序(reverse topological sort)來處理子問題圖中的頂點。
    • 帶備忘機制的自頂向下動態規劃算法是按照深度優先搜索(depth-first search)來處理子問題圖中的頂點。
  • 關係:
    • 子問題圖G = (V, E),因爲每個子問題只求解一次,所以動態規劃算法運行時間等於每個子問題求解時間之和。
    • 通常,一個子問題的求解時間與子問題圖中對應頂點的出度成正比。
    • 動態規劃算法的運行時間與頂點和邊的數量呈線性關係。

二、經典問題

1、鋼條切割(將長鋼條切割成短鋼條,使得總價值最高)

(1)思路:

  • 當完成初次切割後,將兩段鋼條看成兩個獨立的鋼條切割問題實例。
  • 通過組合兩個相關子問題的最優解,並在所有可能的兩段切割方案中選取組合收益最大者,構成原問題的最優解。

2、矩陣鏈乘法(用最少的標量乘法操作完成一個矩陣鏈相乘的運算)

(1)完全括號化:一個單一矩陣,或者是兩個完全括號化的矩陣乘積鏈的積,且已外加括號。

  • 如矩陣鏈爲(A1,A2,A3,A4),則有五種完全括號化的矩陣乘積鏈:
    • (A1(A2(A3A4)))
    • ((A1(A2A3))A4)
    • (A1((A2A3)A4))
    • ((A1A2)(A3A4))
    • (((A1A2)A3)A4)
  • 若AB = C中,A爲p * q的矩陣,B爲q * r的矩陣,則C爲p * r的矩陣,計算C的時間爲pqr。因此不同的加括號方式可能會導致不同的計算代價。

(2)思路:

  • 最優化括號方案的結構特徵:
    • 一個非平凡的矩陣鏈乘法問題實例的任何解都需要劃分鏈,而任何最優解都是由子問題實例的最優解構成的。
    • 因此爲了構造一個矩陣鏈乘法問題實例的最優解,我們可以將問題劃分爲兩個子問題(AiAi+1…Ak和Ak+1Ak+2…Aj的最優括號問題),求出子問題實例的最優解,然後將子問題的最優解組合起來。
  • 一個遞歸求子問題最優解的方案:
    • 找到最優分割點k:對於AiAi+1…Aj的最優括號化方案,k有j - i種可能取值,k = i,i + 1,…,j - 1。因此檢查所有可能的情況,找到最優者。
    • m[i, j] = min{m[i, k] + m[k + 1, j] + pi-1*pk*pj},其中i ≤ k < j,若i < j;m[i, j] = 0,若i = j。
    • 用s[i, j]保存AiAi+1…Aj最優括號化方案的分割點位置k,即使得m[i, j]  = m[i, k] + m[k + 1, j] + pi-1*pk*pj成立的k值。
  • 計算最優代價:
    • 過程用一個輔助表m[ 1…n, 1…n ]來保存代價m[ i, j],輔助表s[1..n-1, 2..n]記錄最優值m[i, j]對應的分割點k。

(3)遞歸公式:

  • P(n) = 1, n = 1;P(n) = ∑(從k = 1到k = n - 1)P(k)P(n - k),n ≥ 2。

3、最大連續子序列和

  • 定義:在一個給定的序列{A1, A2, ..., An}中,找出一個連續的子序列{Ai,...,Aj},使得這個連續的子序列的和最大,輸出這個最大的子序列和。
  • 令dp[i]爲以A[i]作爲末尾的連續序列的最大和。
    • dp[i] = max{A[i], dp[i - 1] + A[i]}
  • 最終最大連續子序列和爲數組dp中的最大值。
  • 時間複雜度:O(n)

4、最長遞增子序列(Longest Increasing Subsequence,LIS)

  • 定義:在一個已知序列{A1,A2,...,An}中,取出若干元素(不必連續)組成一個新的序列{Ax,...,Ay},新序列的各個數之間依舊保持原序列中的先後順序,此時稱新序列{Ax,...,Ay}爲原序列的一個子序列。若對子序列中的任意下標x < y有Ax < Ay,則稱該子序列爲原序列的一個遞增子序列。最長遞增子序列問題就是求給定序列的所有遞增子序列中最長的那個子序列長度。
  • 令dp[i]爲以A[i]爲末尾的最長遞增子序列的長度。
    • dp[i] = max{1, dp[j] + 1 | j < i && Aj < Ai}
  • 最終最長遞增子序列的長度即爲數組dp中的最大值。
  • 時間複雜度:O(n²)

5、最長公共子序列(Longest Common Subsequence,LCS)

  • 定義:給定兩個字符串S1和S2,求一個最長公共子串,即求字符串S3,它同時爲S1和S2的子串,且要求它的長度最長,並確定這個長度。
  • 對於長度爲n的字符串S1和長度爲m的字符串S2,令dp[i][j]表示以S1[i]作爲末尾和以S2[j]作爲末尾的最長公共子序列的長度,則dp[n][m]的值即爲最長公共子序列的長度。
    • dp[i][j] = dp[i - 1][j - 1] + 1,S1[i] = S2[j]
    • dp[i][j] = max{ dp[i - 1][j], dp[i][j - 1] },S1[i] != S2[j]
    • 對於邊界,若兩個字符串中的一個爲空串,則:
      • dp[i][0] = 0(0 ≤ i ≤ n)
      • dp[0][j] = 0(0 ≤ j ≤ m)
  • 最終dp[n][m]中保存的值即爲兩個原始字符串的最長公共子序列長度。
  • 時間複雜度:O(nm)

6、0-1揹包問題

  • 定義:有n件物品,每件物品的重量爲w[i],其價值爲v[i],現在有個容量爲m的揹包,如何選擇物品使得裝入揹包物品的價值最大。
  • 令dp[i][j]表示前i個物品裝進容量爲j的揹包能獲得的最大價值。則dp[n][m]就是0-1揹包問題的解。
    • dp[i][j] = max{ dp[i - 1][j], dp[i - 1][ j - w[i] ] + v[i] }
      • dp[i][j] = dp[i - 1][j],第i件物品不放入
      • dp[i][j] = dp[i - 1][ j - w[i] ] + v[i],第i件物品放入,其中j - w[i] ≥ 0,表示揹包的容量可以放入第i件物品
    • 邊界情況:
      • dp[i][0] = 0,0 ≤ i ≤ n
      • dp[0][j] = 0,0 ≤ j ≤ m
  • 一維數組的形式:dp[j] = max{ dp[j], dp[ j - w[i] ]  + v[i] },同時保證再每次更新中確定狀態dp[j]時,dp[ j - w[i] ]未被修改,從而完成正確的狀態轉移。因此需要在每次更新中,倒序地遍歷所有j的值。
  • 最終dp[n][m]中保存的值即爲0-1揹包問題的解。
  • 時間複雜度:O(nm)

7、完全揹包問題

  • 定義:有n種物品,每種物品的重量爲w[i],其價值爲v[i],每種物品的數量均爲無限個,現在有容量爲m的揹包,如何選擇物品使得裝入揹包的價值最大?
  • 令dp[i][j]表示前i個物品裝進容量爲j的揹包能獲得的最大價值。則dp[n][m]就是完全揹包問題的解。
  • dp[i][j] = max{ dp[i - 1][j], dp[i][ j - w[i] ] + v[i] }
    • dp[i][j] = dp[i - 1][j],第i件物品不放入
    • dp[i][j] = dp[i][ j - w[i] ] + v[i],第i件物品放入,其中j - w[i] ≥ 0,表示揹包的容量可以放入第i件物品
  • 邊界情況:
    • dp[i][0] = 0,0 ≤ i ≤ n
    • dp[0][j] = 0,0 ≤ j ≤ m
  • 一維數組的形式:dp[j] = max{ dp[j], dp[ j - w[i] ]  + v[i] },同時保證再每次更新中確定狀態dp[j]時,dp[ j - w[i] ]已經被修改,從而完成正確的狀態轉移。因此需要在每次更新中,正序地遍歷所有j的值。
  • 最終dp[n][m]中保存的值即爲完全揹包問題的解。
  • 時間複雜度:O(nm)

8、多重揹包問題

  • 定義:有n種物品,每種物品的重量爲w[i],其價值爲v[i],每種物品的數量均爲k[i],現在有容量爲m的揹包,如何選擇物品使得裝入揹包的價值最大?
  • 將數量爲k的物品拆分爲若干組,將每組物品視爲一件物品,其價值和重量爲該組中所有物品的價值重量綜總和。每組物品包含的原物品個數分別爲2^0,2^1,2^2,...2^c-1,k-2^c+1,其中c是使得k - 2^c + 1 ≥ 0的最大整數。
    • 如k = 14,則2^0,2^1,2^2,14-2^3+1=7,所以爲1,2,4,7可以表示1-14之間的任意數
    • 如k = 15,則2^0,2^1,2^2,2^3,15-2^4+1=0,所以爲1,2,4,8可以表示1-15之間的任意數
    • 如k = 16,則2^0,2^1,2^2,2^3,16-2^4+1=1,所以爲1,2,4,8,1可以表示1-16之間的任意數
  • 時間複雜度爲O(m∑log2(ki)),其中求和從i = 0到i = n。

三、應用實例

1、題目描述: N階樓梯上樓問題:一次可以走兩階或一階,問有多少種上樓方式。(要求採用非遞歸)【華中科技大學】

  • 輸入格式:輸入包括一個整數N(1<=N<90)。
  • 輸出格式:可能有多組測試數據,對於每組數據,輸出當樓梯階數是N時的上樓方式個數。
  • 樣例輸入:
    • 4
  • 樣例輸出:
    • 5

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 91;

long long dp[MAX_N];

int main(){
	int n;
	dp[1] = 1;
	dp[2] = 2;
	for(int i = 3; i <= MAX_N; i++){
		dp[i] = dp[i - 2] + dp[i - 1];
	}
	while(cin >> n){
		cout << dp[n] << endl;
	}
	return 0;
}

2、題目描述:名名的媽媽從外地出差回來,帶了一盒好吃又精美的巧克力給名名(盒內共有 N 塊巧克力,20 > N >0)。 媽媽告訴名名每天可以吃一塊或者兩塊巧克力。 假設名名每天都吃巧克力,問名名共有多少種不同的吃完巧克力的方案。 例如: 如果N=1,則名名第1天就吃掉它,共有1種方案; 如果N=2,則名名可以第1天吃1塊,第2天吃1塊,也可以第1天吃2塊,共有2種方案; 如果N=3,則名名第1天可以吃1塊,剩2塊,也可以第1天吃2塊剩1塊,所以名名共有2+1=3種方案; 如果N=4,則名名可以第1天吃1塊,剩3塊,也可以第1天吃2塊,剩2塊,共有3+2=5種方案。 現在給定N,請你寫程序求出名名吃巧克力的方案數目。【北京大學】

  • 輸入格式:輸入只有1行,即整數N。
  • 輸出格式:可能有多組測試數據,對於每組數據,輸出只有1行,即名名吃巧克力的方案數。
  • 樣例輸入:
    • 4
  • 樣例輸出:
    • 5

示例代碼1:

#include <iostream>

using namespace std;

const int MAX_N = 21;
long long dp[MAX_N];

int main(){
	dp[1] = 1;
	dp[2] = 2;
	for(int i = 3; i < MAX_N; i++){
		dp[i] = dp[i - 1] + dp[i - 2];
	}
	int n;
	while(cin >> n){
		cout << dp[n] << endl;
	}
	return 0;
}

示例代碼2:

#include <iostream>

using namespace std;

int result;

void DFS(int sum, int eat){
	if(sum < eat){
		return;
	}
	if(sum == eat){
		result++;
		return;
	}
	DFS(sum - eat, 1);
	DFS(sum - eat, 2);
	return;
}

int main(){
	int n;
	while(cin >> n){
		result = 0;
		DFS(n, 0);
		cout << result << endl;
	}
	return 0;
}

3、題目描述:給出一個整數序列S,其中有N個數,定義其中一個非空連續子序列T中所有數的和爲T的“序列和”。 對於S的所有非空連續子序列T,求最大的序列和。 變量條件:N爲正整數,N≤1000000,結果序列和在範圍(-2^63,2^63-1)以內。【清華大學】

  • 輸入格式:第一行爲一個正整數N,第二行爲N個整數,表示序列中的數。
  • 輸出格式:輸入可能包括多組數據,對於每一組輸入數據,僅輸出一個數,表示最大序列和。
  • 樣例輸入:
    • 5
    • 1 5 -3 2 4
    • 6
    • 1 -2 3 4 -10 6
    • 4
    • -3 -1 -2 -5
  • 樣例輸出:
    • 9
    • 7
    • -1

示例代碼:

#include <iostream>
#include <vector>

using namespace std;

vector<int> myVector;
vector<long long> dpArray;

void dp(int n){
	dpArray[0] = myVector[0];
	for(int i = 0; i < n - 1; i++){
		if(dpArray[i] + myVector[i + 1] > myVector[i + 1]){
			dpArray[i + 1] = dpArray[i] + myVector[i + 1] ;
		}else{
			dpArray[i + 1] = myVector[i + 1];
		}
	}
}


int main(){
	int n;
	while(cin >> n){
		int inputNumber;
		for(int i = 0; i < n; i++){
			cin >> inputNumber;
			myVector.push_back(inputNumber);
			dpArray.push_back(0);
		}
		dp(n);
		long long max = dpArray[0];
		for(int i = 0; i < n; i++){
			if(dpArray[i] > max){
				max = dpArray[i];
			}
		}
		cout << max << endl;
		myVector.clear();
		dpArray.clear();
	}
	return 0;
}

4、題目描述:已知矩陣的大小定義爲矩陣中所有元素的和。給定一個矩陣,你的任務是找到最大的非空(大小至少是1 * 1)子矩陣。 比如,如下4 * 4的矩陣 0 -2 -7 0 9 2 -6 2 -4 1 -4 1 -1 8 0 -2 的最大子矩陣是 9 2 -4 1 -1 8 這個子矩陣的大小是15。【北京大學】

  • 輸入格式:輸入是一個N * N的矩陣。輸入的第一行給出N (0 < N <= 100)。再後面的若干行中,依次(首先從左到右給出第一行的N個整數,再從左到右給出第二行的N個整數……)給出矩陣中的N2個整數,整數之間由空白字符分隔(空格或者空行)。已知矩陣中整數的範圍都在[-127, 127]。
  • 輸出格式:測試數據可能有多組,對於每組測試數據,輸出最大子矩陣的大小。
  • 樣例輸入:
    • 4
    • 0 -2 -7 0
    • 9 2 -6 2
    • -4 1 -4  1
    • -1 8  0 -2
  • 樣例輸出:
    • 15

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 101;
int a[MAX_N][MAX_N];
int aAddColumn[MAX_N];
int dp[MAX_N];
int maxValue;
int N;

void GetMAXMatrix(int col[]){
	dp[0] = col[0];
	for(int i = 1; i < N; i++){
		dp[i] = max(dp[i - 1] + col[i], col[i]);
		maxValue = max(dp[i], maxValue);
	}
}

int main(){
	while(cin >> N){
		maxValue = -127;
		for(int i = 0; i < N; i++){
			for(int j = 0; j < N; j++){
				cin >> a[i][j];
			}
		}
		for(int row = 0; row < N; row++){ //從第row行開始取
			for(int i = 1; i + row <= N; i++){ //每次取1行到N - row行
				memset(aAddColumn, 0, sizeof(aAddColumn));
				for(int j = 0; j < N; j++){ //每行有N列
					for(int k = 0; k < i; k++){ //把某列的行加起來
						aAddColumn[j] += a[row + k][j]; 
					}
				}
				memset(dp, 0, sizeof(dp));
				GetMAXMatrix(aAddColumn);
			}
		}
		cout << maxValue << endl;
	}
	return 0;
}

5、題目描述:給定K個整數的序列{ N1, N2, ..., NK },其任意連續子序列可表示爲{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K。最大連續子序列是所有連續子序列中元素和最大的一個,例如給定序列{ -2, 11, -4, 13, -5, -2 },其最大連續子序列爲{ 11, -4, 13 },最大和爲20。現在增加一個要求,即還需要輸出該子序列的第一個和最後一個元素。【浙江大學】

  • 輸入格式:測試輸入包含若干測試用例,每個測試用例佔2行,第1行給出正整數K( K< 10000 ),第2行給出K個整數,中間用空格分隔。當K爲0時,輸入結束,該用例不被處理。
  • 輸出格式:對每個測試用例,在1行裏輸出最大和、最大連續子序列的第一個和最後一個元素,中間用空格分隔。如果最大連續子序列不唯一,則輸出序號i和j最小的那個(如輸入樣例的第2、3組)。若所有K個元素都是負數,則定義其最大和爲0,輸出整個序列的首尾元素。
  • 樣例輸入:
    • 6
    • -2 11 -4 13 -5 -2
    • 10
    • -10 1 2 3 4 -5 -23 3 7 -21
    • 6
    • 5 -8 3 2 5 0
    • 1
    • 10
    • 3
    • -1 -5 -2
    • 3
    • -1 0 -2
    • 0
  • 樣例輸出:
    • 20 11 13
    • 10 1 4
    • 10 3 5
    • 10 10 10
    • 0 -1 -2
    • 0 0 0

示例代碼:

#include <iostream>

using namespace std;

const int MAX_N = 10001;

struct Node{
	int dp;
	int begin;
	int end;
	Node(){};
	Node(int d, int b = 0, int e = 0):dp(d), begin(b), end(e){};
};

int input[MAX_N];
Node nodeList[MAX_N];

void MaxSequence(int n){
	nodeList[0] = Node(input[0], input[0], input[0]);
	for(int i = 1; i < n; i++){
		if(nodeList[i - 1].dp + input[i] > input[i]){
			nodeList[i] = Node(nodeList[i - 1].dp + input[i], nodeList[i - 1].begin, input[i]);
		}else{
			nodeList[i] = Node(input[i], input[i], input[i]);
		}
	}
}

int main(){
	int n;
	while(cin >> n && n != 0){
		bool flag = true;
		for(int i = 0; i < n; i++){
			cin >> input[i];
			if(input[i] > 0){
				flag = false;
			}
		}
		if(flag){
			cout << 0 << " " << input[0] << " " << input[n - 1] << endl;
		}else{
			MaxSequence(n);
			int max = -1, index = 0;
			for(int i = 0; i < n; i++){
				if(nodeList[i].dp > max){
					max = nodeList[i].dp;
					index = i;
				}
			}
			cout << max << " " << nodeList[index].begin << " " << nodeList[index].end << endl;
		}
	}
	return 0;
}

6、題目描述:某國爲了防禦敵國的導彈襲擊,開發出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲,並觀測到導彈依次飛來的高度,請計算這套系統最多能攔截多少導彈。攔截來襲導彈時,必須按來襲導彈襲擊的時間順序,不允許先攔截後面的導彈,再攔截前面的導彈。 【北京大學】

  • 輸入格式:每組輸入有兩行,第一行,輸入雷達捕捉到的敵國導彈的數量k(k<=25),第二行,輸入k個正整數,表示k枚導彈的高度,按來襲導彈的襲擊時間順序給出,以空格分隔。
  • 輸出格式:每組輸出只有一行,包含一個整數,表示最多能攔截多少枚導彈。
  • 樣例輸入:
    • 8
    • 300 207 155 300 299 170 158 65
  • 樣例輸出:
    • 6

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 25;

int a[MAX_N];
int dp[MAX_N];

void GetMaxSequencr(int n){
	dp[0] = 1;
	for(int i = 1; i < n; i++){
		int max = 0;
		bool flag = false;
		for(int j = 0; j < i; j++){
			if(dp[j] > max && a[j] >= a[i]){
				max = dp[j];
				flag = true;
			}
		}
		if(flag){
			dp[i] = max + 1;
		}else{
			dp[i] = 1;
		}
	}
}

int main(){
	int n;
	while(cin >> n){
		if(n == 0){
			continue;
		}
		memset(dp, 0, sizeof(dp));
		for(int i = 0; i < n; i++){
			cin >> a[i];
		}
		GetMaxSequencr(n);
		int max = dp[0];
		for(int i = 0; i < n; i++){
			if(dp[i] > max){
				max = dp[i];
			}
		}
		cout << max << endl;
	}
	return 0;
}

7、題目描述:一個數的序列bi,當b1 < b2 < ... < bS的時候,我們稱這個序列是上升的。對於給定的一個序列(a1, a2, ...,aN),我們可以得到一些上升的子序列(ai1, ai2, ..., aiK),這裏1 <= i1 < i2 < ... < iK <= N。比如,對於序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。這些子序列中序列和最大爲18,爲子序列(1, 3, 5, 9)的和. 你的任務,就是對於給定的序列,求出最大上升子序列和。注意,最長的上升子序列的和不一定是最大的,比如序列(100, 1, 2, 3)的最大上升子序列和爲100,而最長上升子序列爲(1, 2, 3)。【北京大學】

  • 輸入格式:輸入包含多組測試數據。每組測試數據由兩行組成。第一行是序列的長度N (1 <= N <= 1000)。第二行給出序列中的N個整數,這些整數的取值範圍都在0到10000(可能重複)。
  • 輸出格式:對於每組測試數據,輸出其最大上升子序列和。
  • 樣例輸入:
    • 7
    • 1 7 3 5 9 4 8
  • 樣例輸出:
    • 18

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 1000;
int a[MAX_N];
int dp[MAX_N];

void MaxAscSequence(int n){
	for(int i = 0; i < n; i++){
		dp[i] = a[i];
		for(int j = 0; j < i; j++){
			if(a[j] < a[i]){
				dp[i] = max(dp[j] + a[i], dp[i]);
			}
		}
	}
}

int main(){
	int n;
	while(cin >> n){
		for(int i = 0; i < n; i++){
			cin >> a[i];
		}
		MaxAscSequence(n);
		int max = 0;
		for(int i = 0; i < n; i++){
			if(dp[i] > max){
				max = dp[i];
			}
		}
		cout << max << endl;
	}
	return 0;
}

8、題目描述:輸入格子的個數爲n,刷3種顏色的顏料,相鄰的格子顏料顏色不能相同,且首尾方格顏色不能相同。每個格子必須塗色。計算一共有多少種塗色方式。【暨南大學】

  • 輸入格式:每組測試數據佔1行,包括一個正整數b(1 <= b <= 50)
  • 輸出格式:輸出塗色方式數目
  • 樣例輸入:
    • 1
    • 2
  • 樣例輸出:
    • 3
    • 6

示例代碼:

#include <iostream>

using namespace std;

const int MAX_N = 51;
long long dp[MAX_N];

void GetMaxPaintMethod(int n){
	dp[0] = 3;
	dp[1] = 6;
	dp[2] = 6;
	for(int i = 3; i < n; i++){
		dp[i] = dp[i - 1] + 2 * dp[i - 2];
	}
}

int main(){
	GetMaxPaintMethod(MAX_N);
	int n;
	while(cin >> n){
		cout << dp[n - 1] << endl;
	}
	return 0;
}

9、題目描述:N位同學站成一排,音樂老師要請其中的(N-K)位同學出列,使得剩下的K位同學不交換位置就能排成合唱隊形。 合唱隊形是指這樣的一種隊形:設K位同學從左到右依次編號爲1, 2, …, K,他們的身高分別爲T1, T2, …, TK, 則他們的身高滿足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。 你的任務是,已知所有N位同學的身高,計算最少需要幾位同學出列,可以使得剩下的同學排成合唱隊形。【北京大學】

  • 輸入格式:輸入的第一行是一個整數N(2 <= N <= 100),表示同學的總數。第一行有n個整數,用空格分隔,第i個整數Ti(130 <= Ti <= 230)是第i位同學的身高(釐米)。
  • 輸出格式:可能包括多組測試數據,對於每組數據,輸出包括一行,這一行只包含一個整數,就是最少需要幾位同學出列。
  • 樣例輸入:
    • 8
    • 186 186 150 200 160 130 197 220
  • 樣例輸出:
    • 4

示例代碼:

#include <iostream>

using namespace std;

const int MAX_N = 101;

int dpDesc[MAX_N];
int dpAsc[MAX_N];
int stu[MAX_N];
int descAnswer;
int ascAnswer;

int GetMaxAscSequence(int n){
	if(n > 2){
		for(int i = 0; i < n; i++){
			dpAsc[i] = 1;
			for(int j = 0; j < i; j++){
				if(stu[i] > stu[j]){
					dpAsc[i] = max(dpAsc[i], dpAsc[j] + 1);
				}
			}
		}
		for(int i = n - 1; i >= 0; i--){
			dpDesc[i] = 1;
			for(int j = n - 1; j > i; j--){
				if(stu[i] > stu[j]){
					dpDesc[i] = max(dpDesc[i], dpDesc[j] + 1);
				}
			}
		}
	}
	int max = 0;
	for(int i = 0; i < n; i++){
		if(dpDesc[i] + dpAsc[i] - 1 > max){
			max = dpDesc[i] + dpAsc[i] - 1;
		}
	}
	return max;
}

int main(){
	int n;
	while(cin >> n){
		for(int i = 0; i < n; i++){
			cin >> stu[i];
		}
		int answer = n - GetMaxAscSequence(n);
		cout << answer << endl;
	}
	return 0;
}

10、題目描述:A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, ..., xm> another sequence Z = <z1, z2, ..., zk> is a subsequence of X if there exists a strictly increasing sequence <i1, i2, ..., ik> of indices of X such that for all j = 1,2,...,k, xij = zj. For example, Z = <a, b, f, c> is a subsequence of X = <a, b, c, f, b, c> with index sequence <1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.【Southeastern Europe 2003】

  • 輸入格式:The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces.The input data are correct.
  • 輸出格式:For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line. 
  • 樣例輸入:
    • abcfbc abfcab
    • programming contest 
    • abcd mnp
  • 樣例輸出:
    • 4
    • 2
    • 0

示例代碼:

#include <iostream>
#include <string>

using namespace std;

const int MAXN = 500;

int dp[MAXN][MAXN];

int main(){
	string s1, s2;
	while(cin >> s1 >> s2){
		s1 = " " + s1;
		s2 = " " + s2;
		for(int i = 0; i < s1.size(); i++){
			for(int j = 0; j < s2.size(); j++){
				if(i == 0 || j == 0){
					dp[i][j] = 0;
				}else if(s1[i] != s2[j]){
					dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
				}else{
					dp[i][j] = dp[i - 1][j - 1] + 1;
				}
			}
		}
		cout << dp[s1.size() - 1][s2.size() - 1] << endl;
	}
	return 0;
}

11、Find a longest common subsequence of two strings.【上海交通大學】

  • 輸入格式:First and second line of each input case contain two strings of lowercase character a…z. There are no spaces before, inside or after the strings. Lengths of strings do not exceed 100.
  • 輸出格式:For each case, output k – the length of a longest common subsequence in one line.
  • 樣例輸入:
    • abcd
    • cxbydz
  • 樣例輸出:
    • 2

示例代碼:

#include <iostream>
#include <string>

using namespace std;

const int MAX_N = 101;
int dp[MAX_N][MAX_N];

int main(){
	string s1, s2;
	while(cin >> s1 >> s2){
		s1 = " " + s1;
		s2 = " " + s2;
		for(int i = 0; i < s1.size(); i++){
			for(int j = 0; j < s2.size(); j++){
				if(i == 0 || j == 0){
					dp[i][j] = 0;
				}else if(s1[i] == s2[j]){
					dp[i][j] = dp[i - 1][j - 1] + 1;
				}else{
					dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
				}
			}
		}
		cout << dp[s1.size() - 1][s2.size() - 1] << endl;
	}
	return 0;
}

12、北大網絡實驗室經常有活動需要叫外賣,但是每次叫外賣的報銷經費的總額最大爲C元,有N種菜可以點,經過長時間的點菜,網絡實驗室對於每種菜i都有一個量化的評價分數(表示這個菜可口程度),爲Vi,每種菜的價格爲Pi, 問如何選擇各種菜,使得在報銷額度範圍內能使點到的菜的總評價分數最大。注意:由於需要營養多樣化,每種菜只能點一次。【北京大學】

  • 輸入格式:輸入的第一行有兩個整數C(1 <= C <= 1000)和N(1 <= N <= 100),C代表總共能夠報銷的額度,N>代表能點菜的數目。接下來的N行每行包括兩個在1到100之間(包括1和100)的的整數,分別表示菜的>價格和菜的評價分數。
  • 輸出格式:輸出只包括一行,這一行只包含一個整數,表示在報銷額度範圍內,所點的菜得到的最大評價分數。
  • 樣例輸入:
    • 90 4
    • 20 25
    • 30 20
    • 40 50
    • 10 18
    • 40 2
    • 25 30
    • 10 8
  • 樣例輸出:
    • 95
    • 38

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_VALUE = 1001;
const int MAX_DISH = 101;

int dish[MAX_DISH][2];
int dp[MAX_VALUE];

int main(){
	int v, d;
	while(cin >> v >> d){
		for(int i = 0; i < d; i++){
			cin >> dish[i][0] >> dish[i][1];//價格 評分
		}
		memset(dp, 0, sizeof(dp));
		for(int i = 0; i < d; i++){
			for(int j = v; j >= dish[i][0]; j--){
				dp[j] = max(dp[j], dp[j - dish[i][0]] + dish[i][1]);
			}
		}
		cout << dp[v] << endl;
	}
}

13、題目描述:辰辰是個很有潛能、天資聰穎的孩子,他的夢想是成爲世界上最偉大的醫師。 爲此,他想拜附近最有威望的醫師爲師。醫師爲了判斷他的資質,給他出了一個難題。 醫師把他帶到個到處都是草藥的山洞裏對他說: “孩子,這個山洞裏有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。 我會給你一段時間,在這段時間裏,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。” 如果你是辰辰,你能完成這個任務嗎?【北京大學】

  • 輸入格式:輸入的第一行有兩個整數T(1 <= T <= 1000)和M(1 <= M <= 100),T代表總共能夠用來採藥的時間,M代表山洞裏的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100)的的整數,分別表示採摘某株草藥的時間和這株草藥的價值。
  • 輸出格式:可能有多組測試數據,對於每組數據,輸出只包括一行,這一行只包含一個整數,表示在規定的時間內,可以採到的草藥的最大總價值。
  • 樣例輸入:
    • 70 3
    • 71 100
    • 69 1
    • 1 2
  • 樣例輸出:
    • 3

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_TIME = 1001;
const int DRUG_NUMBER = 101;

int dp[MAX_TIME];
int times[DRUG_NUMBER];
int values[DRUG_NUMBER];

int main(){
	int T, M;//採藥的時間和草藥的數目
	while(cin >> T >> M){
		for(int i = 0; i < M; i++){
			cin >> times[i] >> values[i];
		}
		memset(dp, 0, sizeof(dp));
		for(int i = 0; i < M; i++){
			for(int j = T; j >= times[i]; j--){
				dp[j] = max(dp[j], dp[j - times[i]] + values[i]);
			}
		}
		cout << dp[T] << endl;
	}
	return 0;
}

14、題目描述:有若干張郵票,要求從中選取最少的郵票張數湊成一個給定的總值。     如,有1分,3分,3分,3分,4分五張郵票,要求湊成10分,則使用3張郵票:3分、3分、4分即可。【清華大學】

  • 輸入格式:有多組數據,對於每組數據,首先是要求湊成的郵票總值M,M<100。然後是一個數N,N〈20,表示有N張郵票。接下來是N個正整數,分別表示這N張郵票的面值,且以升序排列。
  • 輸出格式:對於每組數據,能夠湊成總值M的最少郵票張數。若無解,輸出0。
  • 樣例輸入:
    • 10
    • 5
    • 1 3 3 3 4
  • 樣例輸出:
    • 3

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_STAMP = 21;
const int MAX_VALUE = 101;

int dp[MAX_VALUE];
int stamp[MAX_STAMP];

int main(){
	int totalValue, stampNumber;
	while(cin >> totalValue >> stampNumber){
		for(int i = 0; i < stampNumber; i++){
			cin >> stamp[i];
		}
		for(int i = 0; i <= totalValue; i++){
			dp[i] = MAX_VALUE;
		}
		dp[0] = 0;
		for(int i = 0; i < stampNumber; i++){
			//能夠湊成,且數量變少就取代
			for(int j = totalValue; j >= stamp[i]; j--){
				if(dp[j - stamp[i]] == MAX_VALUE){
					continue;
				}
				if(dp[j - stamp[i]] + 1 < dp[j]){
					dp[j] = dp[j - stamp[i]] + 1;
				}
			}
		}
		if(dp[totalValue] == MAX_VALUE){
			cout << 0 << endl;
		}else{
			cout << dp[totalValue] << endl;
		}
	}
	return 0;
}

15、題目描述:Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money, he takes all the coins and throws them into a piggy-bank(儲錢罐). You know that this process is irreversible(不可逆的), the coins cannot be removed without breaking the pig. After a sufficiently long time, there should be enough cash in the piggy-bank to pay everything that needs to be paid.
But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly, we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely(過早地) broken pigs!【Central Europe 1999】

  • 輸入格式:The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1 <= E <= F <= 10000. On the second line of each test case, there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, P and W (1 <= P <= 50000, 1 <= W <=10000). P is the value of the coin in monetary units(貨幣單位), W is it's weight in grams.
  • 輸出格式:Print exactly one line of output for each test case. The line must contain the sentence "The minimum amount of money in the piggy-bank is X." where X is the minimum amount of money that can be achieved using coins with the given total weight. If the weight cannot be reached exactly, print a line "This is impossible.".
  • 樣例輸入:
    • 3
    • 10 110
    • 2
    • 1 1
    • 30 50
    • 10 110
    • 2
    • 1 1
    • 50 30
    • 1 6
    • 2
    • 10 3
    • 20 4
  • 樣例輸出:
    • The minimum amount of money in the piggy-bank is 60.【2個價值爲30,重爲50的硬幣】
    • The minimum amount of money in the piggy-bank is 100.【100個價值爲1元,重爲1g的硬幣】
    • This is impossible.

示例代碼:

#include <iostream>

using namespace std;

const int MAX_WEIGHT = 100001;
const int COIN_TYPE = 501;
const int INF = 0x1fffffff;

int pValue[COIN_TYPE];
int weight[COIN_TYPE];

int dp[MAX_WEIGHT];

int main(){
	int emptyPot, fullPot, coinType;
	int caseNumber;
	while(cin >> caseNumber){
		for(int currCase = 0; currCase < caseNumber; currCase++){
			cin >> emptyPot >> fullPot >> coinType;
			for(int i = 0; i < coinType; i++){
				cin >> pValue[i] >> weight[i];
			}
			int potVolume = fullPot - emptyPot;
			for(int i = 0; i <= potVolume; i++){
				dp[i] = INF;
			}
			dp[0] = 0;
			for(int i = 0; i < coinType; i++){
				for(int j = weight[i]; j <= potVolume; j++){
					dp[j] = min(dp[j], dp[j - weight[i]] + pValue[i]);
				}
			}
			if(dp[potVolume] == INF){
				cout << "This is impossible." << endl;
			}else{
				cout << "The minimum amount of money in the piggy-bank is " << dp[potVolume] << "." << endl;
			}
		}
	}
	return 0;
}

16、題目描述:急!災區的食物依然短缺!
爲了挽救災區同胞的生命,心繫災區同胞的你準備自己採購一些糧食支援災區,現在假設你一共有資金n元,而市場有m種大米,每種大米都是袋裝產品,其價格不等,並且只能整袋購買。
請問:你用有限的資金最多能採購多少公斤糧食呢?
後記:
人生是一個充滿了變數的生命過程,天災、人禍、病痛是我們生命歷程中不可預知的威脅。
月有陰晴圓缺,人有旦夕禍福,未來對於我們而言是一個未知數。那麼,我們要做的就應該是珍惜現在,感恩生活——
感謝父母,他們給予我們生命,撫養我們成人;
感謝老師,他們授給我們知識,教我們做人
感謝朋友,他們讓我們感受到世界的溫暖;
感謝對手,他們令我們不斷進取、努力。
同樣,我們也要感謝痛苦與艱辛帶給我們的財富~【2008-06-18《 ACM程序設計》期末考試——四川加油!中國加油!】

  • 輸入格式:輸入數據首先包含一個正整數C,表示有C組測試用例,每組測試用例的第一行是兩個整數n和m(1<=n<=100, 1<=m<=100),分別表示經費的金額和大米的種類,然後是m行數據,每行包含3個數p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分別表示每袋的價格、每袋的重量以及對應種類大米的袋數。
  • 輸出格式:對於每組測試數據,請輸出能夠購買大米的最多重量,你可以假設經費買不光所有的大米,並且經費你可以不用完。每個實例的輸出佔一行。
  • 樣例輸入:
    • 1
    • 8 2
    • 2 100 4
    • 4 100 2
  • 樣例輸出:
    • 400

示例代碼:

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

const int MAX_N = 101 * 20;
int dp[MAX_N];
int w[MAX_N];
int v[MAX_N];
vector<int> tmpList;

vector<int> getList(int n){
	tmpList.clear();
	if(n == 1){
		tmpList.push_back(1);
	}else{
		int tmp = 1, count = n;
		while(tmp <= count){
			tmpList.push_back(tmp);
			count -= tmp;
			tmp *= 2;
		}
		if(count != 0){
			tmpList.push_back(count);
		}
	}
	return tmpList;
}

int main(){
	int caseNumber, money, category, weight, value, bagNumber;
	while(cin >> caseNumber){
		for(int m = 0; m < caseNumber; m++){
			cin >> money >> category;
			int index = 0;
			for(int i = 0; i < category; i++){
				cin >> value >> weight >> bagNumber;
				vector<int> list = getList(bagNumber);
				for(int j = 0; j < list.size(); j++){
					v[index] = list[j] * value;
					w[index] = list[j] * weight;
					index++;
				}
			}
			memset(dp, 0, sizeof(dp));
			for(int i = 0; i < index; i++){
				for(int j = money; j >= v[i]; j--){
					dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
				}
			}
			cout << dp[money] << endl;
		}
	}
	return 0;
}

17、題目描述:Figure 1 shows a number triangle. Write a program that calculates the highest sum of numbers passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally(對角線) down to the left or diagonally down to the right.【POJ 1163】

  • 輸入格式:Your program is to read from standard input. The first line contains one integer N: the number of rows in the triangle. The following N lines describe the data of the triangle. The number of rows in the triangle is > 1 but <= 100. The numbers in the triangle, all integers, are between 0 and 99.
  • 輸出格式:Your program is to write to standard output. The highest sum is written as an integer.
  • 樣例輸入:
    • 5
    • 7
    • 3 8
    • 8 1 0 
    • 2 7 4 4
    • 4 5 2 6 5
  • 樣例輸出:
    • 30

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 101;
int a[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];

int main(){
	int number;
	while(cin >> number){
		memset(a, 0, sizeof(a));
		int row = 1;
		while(row <= number){
			for(int j = 1; j <= row; j++){
				cin >> a[row][j];
			}
			row++;
		}
		for(int i = 1; i <= number; i++){
			for(int j = 1; j <= number; j++){
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
			}
		}
		int max = 0;
		for(int j = 1; j <= number; j++){
			if(dp[number][j] > max){
				max = dp[number][j];
			}
		}
		cout << max << endl;
	}
	return 0;
}

18、把M個同樣的蘋果放在N個同樣的盤子裏,允許有的盤子空着不放,問共有多少種不同的分法?(用K表示)5,1,1和1,5,1 是同一種分法。【北京大學】

  • 輸入格式:每行均包含二個整數M和N,以空格分開。1<=M,N<=10。
  • 輸出格式:對輸入的每組數據M和N,用一行輸出相應的K。
  • 樣例輸入:
    • 7 3
  • 樣例輸出:
    • 8

示例代碼:

#include <iostream>

using namespace std;

const int MAX_N = 11;

int main(){
	int M, N;//M個蘋果,N個盤子
	int dp[MAX_N][MAX_N];
	while(cin >> M >> N){
		for(int i = 0; i <= M; i++){
			for(int j = 0; j <= N; j++){
				if(i == 0 || j == 0 || i == 1 || j == 1){
					dp[i][j] = 1;
					continue;
				}
				if(i < j){
					dp[i][j] = dp[i][i];
				}else{
					dp[i][j] = dp[i - j][j] + dp[i][j - 1];
				}
			}
		}
		cout << dp[M][N] << endl;
	}
	return 0;
}

19、一個整數總可以拆分爲2的冪的和,例如: 7=1+2+4 7=1+2+2+2 7=1+1+1+4 7=1+1+1+2+2 7=1+1+1+1+1+2 7=1+1+1+1+1+1+1 總共有六種不同的拆分方式。 再比如:4可以拆分成:4 = 4,4 = 1 + 1 + 1 + 1,4 = 2 + 2,4=1+1+2。 用f(n)表示n的不同拆分的種數,例如f(7)=6. 要求編寫程序,讀入n(不超過1000000),輸出f(n)%1000000000。【清華大學】

  • 輸入格式:每組輸入包括一個整數:N(1<=N<=1000000)。
  • 輸出格式:對於每組數據,輸出f(n)%1000000000。
  • 樣例輸入:
    • 7
  • 樣例輸出:
    • 6

示例代碼:

#include <iostream>

using namespace std;

const int MAX_N = 1000001;
const int MOD = 1000000000;

int weight[25];
int dp[MAX_N];

int main(){
	int n;
	int index = 0;
	for(int i = 1; i < MAX_N; i *= 2){
		weight[index++] = i;
	}
	dp[0] = 1;
	for(int i = 0; i < index; i++){
		for(int j = weight[i]; j <= MAX_N; j++){
			dp[j] = (dp[j - weight[i]] % MOD + dp[j] % MOD) % MOD;
		}
	}
	while(cin >> n){
		cout << dp[n] << endl;
	}
	return 0;
}

20、題目描述:You are in the world of mathematics to solve the great "Monkey Banana Problem". It states that, a monkey enters into a diamond shaped two dimensional array and can jump in any of the adjacent cells down from its current position (see figure). While moving from one cell to another, the monkey eats all the bananas kept in that cell. The monkey enters into the array from the upper part and goes out through the lower part. Find the maximum number of bananas the monkey can eat.【light oj 1004】

  • 輸入格式:Input starts with an integer T (≤ 50), denoting the number of test cases.Every case starts with an integer N (1 ≤ N ≤ 100). It denotes that, there will be 2*N - 1 rows. The i^th(1 ≤ i ≤ N) line of next N lines contains exactly i numbers. Then there will be N - 1 lines. The j^th(1 ≤ j < N) line contains N - j integers. Each number is greater than zero and less than 2^15.
  • 輸出格式:For each case, print the case number and maximum number of bananas eaten by the monkey.
  • 樣例輸入:
    • 2
    • 4
    • 7
    • 6 4
    • 2 5 10
    • 9 8 12 2
    • 2 12 7
    • 8 2
    • 10
    • 2
    • 1
    • 2 3
    • 1
  • 樣例輸出:
    • Case 1: 63
    • Case 2: 5

示例代碼:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 101;

int main(){
	int caseNumber, row;
	cin >> caseNumber;
	int dp[MAX_N][MAX_N];
	int banana[MAX_N][MAX_N];
	for(int c = 1; c <= caseNumber; c++){
		memset(banana, 0, sizeof(banana));
		memset(dp, 0, sizeof(dp));
		cin >> row;
		for(int i = 1; i <= row; i++){
			for(int j = 1; j <= i; j++){
				cin >> banana[i][j];
			}
		}
		for(int i = row + 1; i <= 2 * row - 1; i++){
			for(int j = 1; j <= 2 * row - i; j++){
				cin >> banana[i][j];
			}
		}
		for(int i = 1; i <= row; i++){
			for(int j = 1; j <= row; j++){
				dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + banana[i][j];
			}
		}
		for(int i = row + 1; i <= 2 * row - 1; i++){
			for(int j = 1; j <= row; j++){
				dp[i][j] = max(dp[i - 1][j + 1], dp[i - 1][j]) + banana[i][j];
			}
		}
		cout << "Case " << c << ": " << dp[2 * row - 1][1] << endl;
	}
	return 0;
}

參考文獻:

[1]Thomas.H.Cormen Charles E. Leiseron、Ronald L. Rivest Clifford Srein. 算法導論(第3版). [M]北京:機械工業出版社,2013.01;
[2]楊澤邦、趙霖. 計算機考研——機試指南(第2版). [M]北京:電子工業出版社,2019.11;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章