1. 程式人生 > >石子合併 動態規劃(環形)

石子合併 動態規劃(環形)

1、問題描述:問題來源 NWPU noj 1148

在一個圓形操場的四周擺放著n堆石子(n<= 100),現要將石子有次序地合併成一堆。規定每次只能選取相鄰的兩堆合併成新的一堆,並將新的一堆的石子數,記為該次合併的得分。編一程式,讀入石子堆數n及每堆的石子數(<=20)。選擇一種合併石子的方案,使得做n-1次合併,得分的總和最小; 比如有4堆石子:44 5 9 則最佳合併方案如下:
4 4 5 9 score: 0
8 5 9 score: 8
13 9 score: 8 + 13 = 21
22 score: 8 + 13 + 22 = 43

2、輸入

可能有多組測試資料。 當輸入n=0時結束! 第一行為石子堆數n(1<=n<=100);第二行為n堆的石子每堆的石子數,每兩個數之間用一個空格分隔。

3、輸出

合併的最小得分,每個結果一行。

4、問題解析

這個問題和直線型的區別在於最後一堆和第一堆也是相鄰的,可以把圓形轉換成直線型,把問題擴充套件為2n-1堆石子,舉個例子,如果環形石子堆是4 4 5 9,那麼轉換成直線型就變成了 4 4 5 9 4 4 5,所以最終就不是計算  0~n-1了,而是在 0~n-1,1-n,2-n+1,...,n-1~2n-2中選擇最小的。計算方法和直線型的相同。

5、程式碼如下:(已經提交過驗證)

#include<iostream>
#include<vector>

using namespace std;

int main() {

	int n;//n堆石子
	int i, j, k, sum, tmp;//輔助變數
	while (cin >> n) {
		if (0 == n)break;
		sum = 0;
		tmp = INT_MAX;
		vector<int> arr(2*n-1);
		vector<vector<int> >min(2*n-1, vector<int>(2*n-1));//min[i][j]代表合併第i堆到第j堆的最小得分
		for (i = 0; i < n; ++i) {
			cin >> arr[i];
		}
		for (i = n; i <= 2*n - 2; ++i) {
			arr[i] = arr[i - n];
		}
		//給min賦值
		for (i = 0; i <= 2*n-2; ++i) {
			min[i][i] = 0;//初始化為0
		}
		for (i = 2*n - 3; i >= 0; --i) {
			for (j = i + 1; j <= 2*n-2; ++j) {
				for (k = i; k <= j; ++k) {
						sum += arr[k];
					
					//	cout << "sum的值:" << sum << endl;
				}//end for k
				for (k = i; k < j; ++k) {
					if (tmp > min[i][k] + min[k + 1][j]) {
						tmp = min[i][k] + min[k + 1][j];
					}
				}//end for k
				min[i][j] = sum + tmp;
				sum = 0;
				tmp = INT_MAX;
			}// end for j
		}// end for i
		//選出長度為n的最小的值
		int Min = INT_MAX;
		for (i = 0; i < n; ++i) {
			if (min[i][i + n - 1] < Min)
				Min = min[i][i + n - 1];
		}
		cout <<Min << endl;
		Min = INT_MAX;
	}
}