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;
}