图片解码

逆序还原旋转与翻转操作,用四种逻辑方向表示整图旋转,避免重复搬运大矩阵。

OJ: shumeng

题目 ID: CSP202512C

难度:未知

标签:模拟矩阵坐标映射逆操作

日期: 2026-07-31 16:22

形式化题目

一张图片被补成 Z×ZZ \times Z 方阵后,依次进行 tt 次加密操作,每次为局部顺时针旋转(附整图逆时针旋转 rr 次)或局部上下/左右翻转。给定加密后的方阵和密钥序列(记录每次操作参数),还原原始图片并输出其真实尺寸。

思路

加密操作全部可逆,因此从最后一次操作开始逆序处理。

逆序撤销操作

  • 旋转加密:先撤销整图旋转(把逻辑视图方向回退),再撤销局部正方形的旋转。
  • 翻转加密:上下、左右翻转都是自身的逆操作,直接反向搬回。

用逻辑方向避免整图搬运

整图旋转如果直接搬动 Z2Z^2 个字符会超时。改为维护"逻辑坐标到存储坐标"的四种方向映射,整图旋转只是修改一个枚举方向,实际矩阵不动。局部操作边长最多 10,读入临时数组后按方向映射写回即可。

还原原始尺寸

全部操作逆序完成后,扫描逻辑视图中非 ? 的最大行列,即为原始图片的尺寸。

代码

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-07-31 16:22
 * update_at: 2026-08-17 23:06
 */
#include <bits/stdc++.h>
using namespace std;

int z;                  // 加密方阵边长
int orientation;        // 当前整图旋转方向:0/1/2/3 分别表示 0/90/180/270 度
vector<string> picture; // 存储矩阵(以逻辑视图为准)

// 逻辑坐标 (row,col) 到存储坐标 (mapped_row,mapped_col) 的映射,随整图方向变化
void map_position(int row, int col, int &mapped_row, int &mapped_col) {
    if (orientation == 0) {
        mapped_row = row;
        mapped_col = col;
    } else if (orientation == 1) {
        mapped_row = z - 1 - col;
        mapped_col = row;
    } else if (orientation == 2) {
        mapped_row = z - 1 - row;
        mapped_col = z - 1 - col;
    } else {
        mapped_row = col;
        mapped_col = z - 1 - row;
    }
}

// 读取逻辑坐标 (row,col) 处的字符
char get_cell(int row, int col) {
    int mapped_row, mapped_col;
    map_position(row, col, mapped_row, mapped_col);
    return picture[mapped_row][mapped_col];
}

// 写入逻辑坐标 (row,col) 处的字符
void set_cell(int row, int col, char value) {
    int mapped_row, mapped_col;
    map_position(row, col, mapped_row, mapped_col);
    picture[mapped_row][mapped_col] = value;
}

// 撤销一次局部旋转:把 top,left 处边长 length 的正方形逆时针转回。
// 当前整图方向已提前由调用者更新,这里以逻辑视图操作即可
void undo_rotation(int top, int left, int length, int degree) {
    char old_value[10][10];
    for (int i = 0; i < length; i++) {
        for (int j = 0; j < length; j++) {
            old_value[i][j] = get_cell(top + i, left + j);
        }
    }

    int times = degree / 90;
    for (int i = 0; i < length; i++) {
        for (int j = 0; j < length; j++) {
            int old_row, old_col;
            if (times == 1) {
                old_row = j;
                old_col = length - 1 - i;
            } else if (times == 2) {
                old_row = length - 1 - i;
                old_col = length - 1 - j;
            } else {
                old_row = length - 1 - j;
                old_col = i;
            }
            set_cell(top + i, left + j, old_value[old_row][old_col]);
        }
    }
}

// 撤销一次局部翻转:direction=1 上下翻转,direction=-1 左右翻转。
// 翻转是自身的逆操作,直接反向搬回即可
void undo_flip(int top, int bottom, int left, int right, int direction) {
    int height = bottom - top + 1;
    int width = right - left + 1;
    char old_value[10][10];
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            old_value[i][j] = get_cell(top + i, left + j);
        }
    }
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            int old_row = direction == 1 ? height - 1 - i : i;
            int old_col = direction == -1 ? width - 1 - j : j;
            set_cell(top + i, left + j, old_value[old_row][old_col]);
        }
    }
}

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

    cin >> z;
    picture.resize(z);
    for (int i = 0; i < z; i++) cin >> picture[i];
    int key_length;
    cin >> key_length;
    vector<int> key(key_length);
    for (int i = 0; i < key_length; i++) cin >> key[i];

    int operations = key[0];
    orientation = 0;
    for (int operation = operations - 1; operation >= 0; operation--) {
        int position = 1 + operation * 6;
        int type = key[position];
        if (type == 1) {
            int top = key[position + 1] - 1;
            int left = key[position + 2] - 1;
            int length = key[position + 3];
            int degree = key[position + 4];
            int whole_rotations = key[position + 5];
            orientation = (orientation + whole_rotations) % 4;
            undo_rotation(top, left, length, degree);
        } else {
            int top = key[position + 1] - 1;
            int bottom = key[position + 2] - 1;
            int left = key[position + 3] - 1;
            int right = key[position + 4] - 1;
            int direction = key[position + 5];
            undo_flip(top, bottom, left, right, direction);
        }
    }

    int last_row = -1;
    int last_col = -1;
    for (int i = 0; i < z; i++) {
        for (int j = 0; j < z; j++) {
            if (get_cell(i, j) != '?') {
                last_row = max(last_row, i);
                last_col = max(last_col, j);
            }
        }
    }
    cout << last_row + 1 << ' ' << last_col + 1 << '\n';
    for (int i = 0; i <= last_row; i++) {
        for (int j = 0; j <= last_col; j++) cout << get_cell(i, j);
        cout << '\n';
    }
    return 0;
}

复杂度

每次局部操作处理至多 10×1010 \times 10 个字符,总时间复杂度 O(t100+Z2)O(t \cdot 100 + Z^2),空间复杂度 O(Z2)O(Z^2)

总结

对经常整图旋转的矩阵,把旋转记录在坐标映射中而不是真的搬运数据,能省下大量时间。局部操作规模很小,直接读临时数组再写回即可。注意逆序撤销时先处理整图方向、再处理局部旋转。