1. 程式人生 > >UVA437 The Tower of Babylon

UVA437 The Tower of Babylon

題意翻譯

【題目】

你可能已經聽說過巴比倫塔的傳說。現在這個傳說的許多細節已經被遺忘。所以本著本場比賽的教育性質,我們現在會告訴你整個傳說:

巴比倫人有n種長方形方塊,每種有無限個,第i種方塊的三邊邊長是xi,yi,zi。對於每一個方塊,你可以任意選擇一面作為底,這樣高就隨著確定了。舉個例子,同一種方塊,可能其中一個是豎著放的,一個是側著放的,一個是橫著放的。

他們想要用堆方塊的方式建儘可能高的塔。問題是,只有一個方塊的底的兩條邊嚴格小於另一個方塊的底的兩條邊,這個方塊才能堆在另一個上面。這意味著,一個方塊甚至不能堆在一個底的尺寸與它一樣的方塊的上面。

你的任務是編寫一個程式,計算出這個塔可以建出的最高的高度。

【輸入】

輸入會包含至少一組資料,每組資料的第一行是一個整數n(n<=30),表示方塊的種類數。 這組資料接下來的n行,每行有三個整數,表示xi,yi,zi。 輸入資料會以0結束。

【輸出】

對於每組資料,輸出一行,其中包含組號(從1開始)和塔最高的高度。按以下格式: Case : maximum height = __

【輸入樣例】

1

10 20 30

2

6 8 10

5 5 5

7

1 1 1

2 2 2

3 3 3

4 4 4

5 5 5

6 6 6

7 7 7

5

31 41 59

26 53 58

97 93 23

84 62 64

33 83 27

0

【輸出樣例】

Case 1: maximum height = 40

Case 2: maximum height = 21

Case 3: maximum height = 28

Case 4: maximum height = 342

題解

詳細可以檢視lrj書。本題題解出自劉汝佳演算法競賽入門經典

在任何時候,只有頂面的尺寸會影響到後續決策,因此可以用二元組(a,b)來表示“頂面尺寸為 a * b”這個狀態。因為每次增加個立方體以後頂面的長和寬都會嚴格減小,所以這個圖是DAG,可以套用前面學過的DAG最長路演算法。這個演算法沒問題,不過落實到程式上時會遇到一個問題:不能直接用d(a,b)表示狀態值,因為a和b可能會很大。怎麼辦呢?可以用(idx, k)這個二元組來“間接”表達這個狀態,其中idx為頂面立方體的序號,k是高的序號(假設輸入時把每個立方體的3個維度從小到大排序,編號為0~2)。例如,若立方體3的大小為a * b * c(其中a≤b≤c),則狀態(3,1)就是指這個立方體在頂面,且高是b(因此頂面大小為a * c)。因為idx是0~n-1的整數,k是0~2的整數,所以可以很方便地用二維陣列來存取。狀態總數是O(n)的,每個狀態的決策有O(n)個,時間複雜度為O(n 2 )。

本題屬於DAG的動態規劃問題

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 35;
int d[maxn][3], blocks[maxn][3];
int n, kase;
void getDimensions(int *v, int idx, int k){
	int index = 0;
	for(int i = 0; i < 3; ++i) if(i != k){
		v[index++] = blocks[idx][i];
	}
}
int dp(int i, int j){
	int &ans = d[i][j];
	if(ans > 0) return ans;
	int v1[2], v2[2];
	getDimensions(v1, i, j);
	for(int a = 0; a < n; ++a)
		for(int b = 0; b < 3; ++b){
			getDimensions(v2, a, b);
			if(v2[0] < v1[0] && v2[1] < v1[1]) ans = max(ans, dp(a, b));
		}
	return ans += blocks[i][j];
}
int main(){
	freopen("data.in", "r", stdin);
	while(scanf("%d", &n) == 1 && n){
		memset(d, 0, sizeof(d));
		for(int i = 0; i < n; ++i){
			for(int j = 0; j < 3; ++j) 
				scanf("%d", &blocks[i][j]);
			sort(blocks[i], blocks[i] + 3);
		}
		int ans = 0;
		for(int i = 0; i < n; ++i)
			for(int j = 0; j < 3; ++j){
				ans = max(ans, dp(i, j));
			}
		printf("Case %d: maximum height = %d\n", ++kase, ans);
	}
}

不過一定要這樣做嗎?

顯然還有其它做法,那麼是什麼呢?其實仔細看,會發現,其實這就是求最長遞增子序列長度啊,只是這個字母帶了權重。。。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1010;
int dp[maxn];
int n, m, kase;
struct data{
	int a, b, c;
	bool operator < (const data &rhs) const {
		return a * b < rhs.a * rhs.b;
	}
}blocks[maxn];
int main(){
	freopen("data.in", "r", stdin);
	while(scanf("%d", &n) == 1 && n){
		m = 0;
		for(int i = 0; i < n; ++i){
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			blocks[m].a = a; blocks[m].b = b; blocks[m++].c = c;
			blocks[m].a = a; blocks[m].b = c; blocks[m++].c = b;
			blocks[m].a = b; blocks[m].b = a; blocks[m++].c = c;
			blocks[m].a = b; blocks[m].b = c; blocks[m++].c = a;
			blocks[m].a = c; blocks[m].b = b; blocks[m++].c = a;
			blocks[m].a = c; blocks[m].b = a; blocks[m++].c = b;
		}
		memset(dp, 0, sizeof(dp));
		sort(blocks, blocks + m);
		int ans = 0;
		for(int i = 0; i < m; ++i){
			dp[i] = blocks[i].c;
			for(int j = 0; j < i; ++j){
				if(blocks[j].a < blocks[i].a && blocks[j].b < blocks[i].b){
					dp[i] = max(dp[i], dp[j] + blocks[i].c);
				}
			}
			ans = max(ans, dp[i]);
		}
		printf("Case %d: maximum height = %d\n", ++kase, ans);
	}
	return 0;
}