【问题描述】
将一个圆盘的圆周用等距的 n 个单位(点)划分,点的编号为顺时针从1~n,即点 i 和 i+1 是相邻点,n 和 1也是相邻点。圆盘上有 m 条线段,它们的端点都在前面的 n 个点上。
那么,给定一个这样的圆盘后,怎样确定这个圆盘是否为旋转对称的?也就是说,选定一个正整数 k ,绕圆心顺时针旋转 k 个单位后,新的图像是否跟原有的形状是相同的?
【输入形式】
输入的第一行为整数 T , 表示测试数据的组数,接下来是 T 组数据。
每组数据的第一行包含两个正整数 n 和 m (2≤ n ≤ 100000,1≤ m ≤ 100000),表示圆周上点的个数以及线段数。
接下来的 m 行,第 i 行为两个整数 a(i) 和 b(i )(1 ≤ a(i) , b(i )≤ n, a(i) ≠ b(i) ),表示一个线段的两个端点。
输入保证不会有相同的线段。
【输出形式】
输出有 T 行,每行为Yes或No,如果某组数据所表示的圆盘式旋转对称的,则输出Yes,否则输出No.
【样例输入】
4
12 6
1 3
3 7
5 7
7 11
9 11
11 3
9 6
4 5
5 6
7 8
8 9
1 2
2 3
10 3
1 2
3 2
7 2
10 2
1 6
2 7
【样例输出】
Yes
Yes
No
Yes
【评判说明】
对于50%的测试数据, n, m ≤ 1000
对于100%的测试数据,n, m ≤ 100000
【样例说明】
前两个圆盘表示如下,当它们顺时针旋转120(o)后,其形状与原始形状是相同的。
解题思路
-
理解旋转对称的定义:一个图形是旋转对称的,意味着它可以在不改变形状的情况下,围绕中心旋转某个角度后与原图重合。对于本题,这意味着存在一个小于n的正整数k,使得每条线段在旋转k个单位后,仍然可以在图中找到一条相同的线段。
-
寻找可能得旋转对称点:因为圆盘是由n个等分点组成,所以旋转对称的角度必然是360°除以n的整数倍。换句话说,旋转对称的步长k必须是n的约数。
-
检查每个可能的k值:对于n的每个约数k(不包括n本身,因为这意味着不旋转),检查通过旋转每条线段k个单位后,是否所有线段都能在圆盘上找到对应的线段。
-
如何表示和比较线段:为了方便比较线段,我们可以为每条线段定义一个规范表示方法。例如,对于线段(ai, bi),我们可以假设ai < bi,这样每条线段都有唯一的表示(对于ai > bi的情况,可以将其转换为(bi, ai))。然后,将这些线段按照某种顺序排序存储,以便于比较。
-
实现细节:在实现时,我们需要注意如何高效地找到n的所有约数,以及如何高效地比较旋转后的线段集合是否与原始集合相同。
Java代码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt(); // 测试数据组数
while (T-- > 0) {
int n = scanner.nextInt(); // 圆周上点的个数
int m = scanner.nextInt(); // 线段数
HashMap<Integer, HashSet<Integer>> edges = new HashMap<>(); // 键代表图中的一个顶点,值包含与该顶点直接相连的所有其他顶点。
// Map<Integer, Set<Integer>>
// 输入1 2: 1 [2]
// 输入1 3: 1 [2,3]
// 输入2 4: 2 [4]
// 旋转后: 1 3
// 旋转后:3 4
for (int i = 0; i < m; i++) { // 循环读取每条线段
// 读取线段的两个端点
int a = scanner.nextInt();
int b = scanner.nextInt();
// 在HashMap中存储图的边
if (edges.containsKey(a)) {
edges.get(a).add(b);
} else {
HashSet<Integer> s = new HashSet<>();
s.add(b);
edges.put(a, s);
}
if (edges.containsKey(b)) {
edges.get(b).add(a);
} else {
HashSet<Integer> s = new HashSet<>();
s.add(a);
edges.put(b, s);
}
}
System.out.println(isRotationSymmetric(n, edges) ? "Yes" : "No");
}
scanner.close();
}
private static boolean isRotationSymmetric(int n, Map<Integer, HashSet<Integer>> edges) {
for (int k = 1; k < n; k++) { // 尝试所有可能的旋转步长k
if (n % k == 0) { // 如果k是n的约数,则尝试以k为旋转步长
boolean symmetric = true; // 假设当前k可以使图形旋转对称
for (int a : edges.keySet()) { // 遍历所有点
for (int b : edges.get(a)) { // 遍历点a连接的所有点
// 计算旋转后的点的位置
int rotatedA = (a - 1 + k) % n + 1;
// a = 11, k = 4
// 期望=3
// (a - 1 + k) % n + 1 = (11 - 1 + 4) % 12 + 1 = 3
int rotatedB = (b - 1 + k) % n + 1;
// 如果旋转后的线段不存在,则设置symmetric为false
// if (edges.containsKey(rotatedA) && !edges.get(rotatedA).contains(rotatedB)) {
if (!edges.containsKey(rotatedA) || !edges.get(rotatedA).contains(rotatedB)) {
symmetric = false;
break;
}
}
if (!symmetric) break; // 如果已确定不对称,跳出循环
}
if (symmetric) return true; // 找到了一个合适的k使得图形旋转对称
}
}
return false; // 没有找到合适的k,图形不旋转对称
}
}