题意翻译
【题目】
你可能已经听说过巴比伦塔的传说。现在这个传说的许多细节已经被遗忘。所以本着本场比赛的教育性质,我们现在会告诉你整个传说:
巴比伦人有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;
}