题目: UVA - 10051Tower of Cubes(递推)
题目大意:给出N个正方体1-N,只有序号小的正方体可以放在序号大的正方体的上面,并且除了最底下的那个正方体,其他的正方体的底面要和它下面的正方体的上面颜色相同。问怎样组合才能使得用的正方体个数越多。并且输出其中的一种堆放方式。
解题思路:一开始觉得是用DAG上的DP来做,结果状态开太多dp【N】【N】【M】(N代表正方体个数, M代表六个面),最后输出路径的时候超时了。后面看了别人的题解,发现状态只需要开dp[N][M]就足够了,因为只要在意这个正方体是哪个面在上面,下面的正方体的范围是确定的,那么组成方式最优的肯定是确定的。不需要将下面接着哪个正方体也记录下来。
状态转移方程:dp【i】【k】 = (dp【i + n】【m】 + 1) m:和第i个正方体底面颜色相同的i + n个正方体的那个面。 i + n 代表i之后的正方体从哪个开始。意思就是从i之后的正方体任选一个作为i后面的那个正方体,然后在用这个i + n的正方题的m面朝上的最多的个数再加上1.
初始化的时候要小心,dp【i】【k】 = 1[(i >= 1 && i <= N) (k >= 0 && k < M)】只取一个的时候,任何一个正方体哪个面朝上的组合方式的最多的个数是1。
这题的路径打印也是很无语,还得先找到对的路径,然后用next【j】(j这个立方体后面接哪个立方体最优),和side【j】(j这个立方体哪个面朝上最优)记录下来,最后在打印出来。
代码:
#include <cstdio>
#include <cstring>
const int N = 505;
const int M = 6;
const char str[M][2 * M] = {"front", "left", "top", "bottom", "right", "back"};
int n;
int cube[N][M];
int dp[N][M];
int next[N], side[N];
int Max (const int a, const int b) {return a > b? a: b;}
bool printf_ans (int d, int k) {
if (dp[d][k] == 1) {
next[d] = d;
return true;
}
for (int j = d + 1; j <= n; j++)
for (int i = 0; i < M; i++)
if (cube[d][M - 1 - k] == cube[j][i] && dp[d][k] == dp[j][i] + 1) {
if (printf_ans(j, i)) {
next[d] = j;
side[j] = i;
return true;
}
}
return false;
}
int main () {
int cas = 0;
while (scanf ("%d", &n) , n) {
if (cas)
printf ("\n");
for (int i = 1; i <= n; i++) {
for (int j = 0; j < M/2; j++)
scanf ("%d%d", &cube[i][j], &cube[i][M - 1 - j]);
}
//init
for (int j = 1; j <= n; j++)
for (int i = 0; i < M; i++)
dp[j][i] = 1;
int ans = 1;
int d, k;
for (int i = n - 1; i >= 1; i--)
for (int j = 0; j < M; j++) {
for (int l = i + 1; l <= n; l++) {
for (int k = 0; k < M; k++)
if (cube[i][M - 1 - j] == cube[l][k]) {
if (dp[l][k] + 1 > dp[i][j]) {
dp[i][j] = Max (dp[i][j], dp[l][k] + 1);
}
}
}
if (dp[i][j] > ans) {
d = i;
k = j;
ans = dp[i][j];
}
}
printf ("Case #%d\n%d\n", ++cas, ans);
//path
if (ans == 1) {
printf ("1 front\n");
continue;
}
printf_ans (d, k);
while (1) {
printf ("%d %s\n", d, str[k]);
if (d == next[d])
break;
d = next[d];
k = side[d];
}
}
return 0;
}