[NOIP2008-普及] 立体图

GitHub跳转原题关系图返回列表

按后→前、左→右、下→上的画家算法,把固定 7×6 字形覆盖到字符画布上生成立体图。

OJ: roj

题目 ID: 3563

难度:普及

标签:模拟python

创建: 2026-10-02 07:50

更新: 2026-10-06 14:09

题目描述

给定 m×nm \times n 的积木高度图(m,n≤50m,n \le 50),画出其立体图:每个单位积木用 +---+、/ /| 等字符拼出正视投影,输出字符矩阵。

思路

按"从后往前、从左往右"的次序画每根柱子:先画顶面 +---+ 与侧面 /、|,高处的柱子自然遮挡低处,逐格把对应字符写入画布,最后逐行输出。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 08:04
# update_at: 2026-10-02 08:04

import sys

# 一个积木的字形:以前面左下角顶点为锚点 (0,0),dy 向上为负、dx 向右为正。
# 三张可见面(前面、上面、右面)连边框一起整格落笔,面的内部写空格,
# 这样后画的积木能先一步抹掉被遮挡的笔画,遮挡无需单独判断。
GLYPH = (
    (-5, 2, "+---+"),  # 上面的后沿
    (-4, 1, "/   /|"),  # 上面的左右斜边与内部
    (-3, 0, "+---+ |"),  # 前面的上沿 + 右面的上半
    (-2, 0, "|   | +"),  # 前面中段 + 右面内部与后沿端点
    (-1, 0, "|   |/"),  # 前面中段 + 右面的下斜边
    (0, 0, "+---+"),  # 前面的下沿
)
CUBE = [(dy, dx + k, ch) for dy, dx, seg in GLYPH for k, ch in enumerate(seg)]  # 36 格


def paint(canvas: list[list[str]], height: list[list[int]], rows: int) -> None:
    """画家算法:按后排→前排、左→右、下→上,把每个积木的 36 格字形覆盖上画布。

    这个顺序保证右邻盖掉本格右面、前排盖掉后排、上层盖掉下层顶面;
    锚点坐标由斜投影的线性位移给出:行偏移 depth、列每格 4 列、层每层 3 行。
    """
    m, n = len(height), len(height[0])
    for i in range(m):
        depth = 2 * (m - 1 - i)  # 第 i 行相对前排往“后上”偏移:行数与列数共用
        base_y = rows - 1 - depth  # 该行最下层积木锚点所在行(前排在最后一行)
        for j in range(n):
            base_x = 4 * j + depth  # 同一锚点列
            for k in range(height[i][j]):  # k = 0 起自下而上
                y = base_y - 3 * k  # 第 k 层锚点行,每上一层抬 3 行
                for dy, dx, ch in CUBE:
                    canvas[y + dy][base_x + dx] = ch


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    m, n = next(data), next(data)

    height: list[list[int]] = []  # height[i][j] = 第 i 行第 j 列的积木数
    for _ in range(m):
        height.append([next(data) for _ in range(n)])

    # 画布取包络:宽 = 最右锚点 + 字形 6 列 + 1;高使最高塔字形顶行恰为 0。
    width = 4 * (n - 1) + 2 * (m - 1) + 7
    rows = max(3 * height[i][j] + 2 * (m - 1 - i) for i in range(m) for j in range(n)) + 3
    canvas = [["."] * width for _ in range(rows)]

    paint(canvas, height, rows)
    sys.stdout.write("\n".join(map("".join, canvas)) + "\n")


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 14:05
 * update_at: 2026-10-06 14:05
 */
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXM = 55;
const int MAXN = 55;

// 单个积木的 7x6 字形,以锚点 (dy=0,dx=0) 为前面左下角
// 每行: dy 偏移, dx 起始偏移, 字符串
struct GlyphRow {
    int dy;
    int dx;
    const char *s;
};

const GlyphRow GLYPH[6] = {
    {-5, 2, "+---+"},   // 上面后沿
    {-4, 1, "/   /|"},  // 上面
    {-3, 0, "+---+ |"}, // 前面上沿 + 右面上半
    {-2, 0, "|   | +"}, // 前面 + 右面
    {-1, 0, "|   |/"},  // 前面 + 右面下斜
    {0,  0, "+---+"},   // 前面下沿
};

// 展开成 36 个落笔点,方便直接覆盖
struct Cell {
    int dy;
    int dx;
    char ch;
};
Cell cube[36];
int cube_cnt = 0;

int m, n;
int h[MAXM][MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // 展开字形到 cube[]
    for (int r = 0; r < 6; ++r) {
        int dy = GLYPH[r].dy;
        int dx0 = GLYPH[r].dx;
        const char *s = GLYPH[r].s;
        for (int k = 0; s[k]; ++k) {
            cube[cube_cnt].dy = dy;
            cube[cube_cnt].dx = dx0 + k;
            cube[cube_cnt].ch = s[k];
            ++cube_cnt;
        }
    }

    if (!(cin >> m >> n)) return 0;
    for (int i = 0; i < m; ++i)
        for (int j = 0; j < n; ++j)
            cin >> h[i][j];

    // 画布包络尺寸
    int width = 4 * (n - 1) + 2 * (m - 1) + 7;
    int rows = 0;
    for (int i = 0; i < m; ++i)
        for (int j = 0; j < n; ++j)
            rows = max(rows, 3 * h[i][j] + 2 * (m - 1 - i));
    rows += 3; // 顶行到锚点还需 3 行余量

    // 画布,背景用 '.'
    vector<string> canvas(rows, string(width, '.'));

    // 画家算法:后排->前排,左->右,下层->上层
    for (int i = 0; i < m; ++i) {
        int depth = 2 * (m - 1 - i);          // 行 i 相对前排的后移量
        int base_y = rows - 1 - depth;        // 该行最下层锚点行
        for (int j = 0; j < n; ++j) {
            int base_x = 4 * j + depth;       // 锚点列
            for (int k = 0; k < h[i][j]; ++k) {
                int y = base_y - 3 * k;       // 第 k 层锚点行
                for (int c = 0; c < cube_cnt; ++c) {
                    int rr = y + cube[c].dy;
                    int cc = base_x + cube[c].dx;
                    canvas[rr][cc] = cube[c].ch;
                }
            }
        }
    }

    for (int i = 0; i < rows; ++i)
        cout << canvas[i] << "\n";
    return 0;
}