图形对称等价于染色集合在镜像变换下封闭,把每个红格子的镜像格子也标记出来,逐格检查一次。
OJ: roj
题目 ID: 19997
难度:普及-
标签:哈希网格思维
日期: 2026-08-28 19:52
形式化题目
有一个
给定一个长度为
等价表述:设红格编号集合为
用
思路
一句话本质:图形对称
直接照"图形对称"检查,需要检查哪些格子?
对称意味着图形沿竖直中线翻折后与自身重合,所以每个被染红的格子,它的镜像格子也必须被染红;反过来不需要再查——镜像的镜像还是自己(翻折两次回到原位),从红格子出发查一遍就覆盖了所有必须检查的点。下图是 3×3 的例子,格子 4 与 6 互为镜像(行不变,列
列: 1 2 3
行1: . . .
行2: 4 . 6 ← 4 与 6 互为镜像:行不变,列 1 → 3
行3: . . .从图中可确认两件事:行号完全不变(两个镜像格子都停在行 2);列号
镜像格子对应的编号怎么算?
为了方便余数的计算,这里统一用
+---+----+----+----+----+----+
| | 0 | 1 | 2 | 3 | 4 | ← 列偏移 r
+---+----+----+----+----+----+
| 0 | 0 | 1 | 2 | 3 | 4 | ← 行偏移 q
| 1 | 5 | 6 | 7 | 8 | 9 |
| 2 | 10 | 11 | 12 | 13 | 14 |
| 3 | 15 | 16 | 17 | 18 | 19 |
| 4 | 20 | 21 | 22 | 23 | 24 |
+---+----+----+----+----+----+例如第 2 行第 3 列的格子是
按行优先编号
为什么对
编号从 1 开始(第 1 行第 1 列的编号是 1),列号
为什么"每个红格的镜像都在集合里"就足以推出整个图形对称?
设红格集合为
不需要按格子开数组。真正被染红的只有 map<long long, bool> 只存这些编号即可,再对输入的 brute.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-08-28 19:05
* update_at: 2026-08-28 19:05
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
// 只适用于 n*m 很小的数据(这里限制 n,m <= 100):
// 直接开二维数组标记每个格子是否被染红,再对每个红格子检查镜像格子是否被染。
// 这是"逐格模拟"的朴素写法;n,m 达到 1e7 时网格无法开数组,就是 main.cpp 要解决的瓶颈。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int a, n, m;
int f[MAXN][MAXN]; // f[x][y] = 1 表示第 x 行第 y 列被染红
int s[10005];
// 把编号 x 转成 (x行, y列):行优先编号 (x-1)*m + y = 编号。
void decode(long long x, int &row, int &col) {
row = (int)((x - 1) / m) + 1;
col = (int)((x - 1) % m) + 1;
}
void solve() {
cin >> a >> n >> m;
memset(f, 0, sizeof(f));
for (int i = 1; i <= a; i++) {
cin >> s[i];
int x, y;
decode(s[i], x, y);
f[x][y] = 1;
}
// 对每个被染红的格子,检查它关于竖直中线(列 y 映到 m-y+1)的镜像是否被染。
for (int i = 1; i <= a; i++) {
int x, y;
decode(s[i], x, y);
if (f[x][m - y + 1] == 0) {
cout << "No\n";
return;
}
}
cout << "Yes\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}重复染色怎么办?
重复染红不改变集合,map 赋值与查询天然按集合语义去重,同一编号出现多次没有影响。
边界情况需要注意什么?
Yes,无需特判。int 范围,必须用 long long。
代码
/**
* 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-08-28 19:05
* update_at: 2026-08-28 19:05
*/
// main.cpp:B. Color 最终解。
// 编号公式按官方 std 与数据采用行优先 (i-1)*m + j 理解(题面原文 (i-1)*n+j 为笔误)。
// 对称性等价于:每个红格子的镜像格子也是红的。
#include <bits/stdc++.h>
using namespace std;
const int MAXA = 100005;
int T;
int a;
long long n, m;
long long s[MAXA]; // 输入的染色编号(可能重复)
map<long long, bool> red; // 被染红的格子集合(重复染色不改变集合)
// 计算格子编号 x 关于竖直中线(列 j 映到 m-j+1)的镜像格子编号。
// 记 x - 1 = t*m + (y-1),即第 t+1 行第 y 列,则镜像为第 t+1 行第 m-y+1 列。
long long trans(long long x) {
long long t = (x - 1) / m; // 所在行(0 起始)
long long y = x - t * m; // 所在列(1..m)
return t * m + (m - y + 1);
}
void solve() {
red.clear();
cin >> a >> n >> m;
for (int i = 1; i <= a; i++) {
cin >> s[i];
red[s[i]] = true;
}
// 逐点检查:每个红格子的镜像必须也在集合中。
// trans 是对合(翻两次回到原位),所以只需从红格子出发查一遍。
for (int i = 1; i <= a; i++) {
long long mir = trans(s[i]);
if (!red.count(mir)) {
cout << "No\n";
return;
}
}
cout << "Yes\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
solve();
}
return 0;
}复杂度
- 时间:每组
( map插入与查询各,共 次)。 - 空间:
( map最多存个不同编号)。 - 与
无关, 、 完全够用。
总结
本题把"图形关于竖直中线对称"翻译成集合问题:染色集合 map 只存