旋转对称(模拟题)

  【问题描述】

   将一个圆盘的圆周用等距的 n 个单位(点)划分,点的编号为顺时针从1~n,即点 ii+1 是相邻点,n 和 1也是相邻点。圆盘上有 m 条线段,它们的端点都在前面的 n 个点上。

   那么,给定一个这样的圆盘后,怎样确定这个圆盘是否为旋转对称的?也就是说,选定一个正整数 k ,绕圆心顺时针旋转 k 个单位后,新的图像是否跟原有的形状是相同的?

  【输入形式】

   输入的第一行为整数 T , 表示测试数据的组数,接下来是 T 组数据。

   每组数据的第一行包含两个正整数 nm (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)后,其形状与原始形状是相同的。

  解题思路

  1. 理解旋转对称的定义:一个图形是旋转对称的,意味着它可以在不改变形状的情况下,围绕中心旋转某个角度后与原图重合。对于本题,这意味着存在一个小于n的正整数k,使得每条线段在旋转k个单位后,仍然可以在图中找到一条相同的线段。

  2. 寻找可能得旋转对称点:因为圆盘是由n个等分点组成,所以旋转对称的角度必然是360°除以n的整数倍。换句话说,旋转对称的步长k必须是n的约数。

  3. 检查每个可能的k值:对于n的每个约数k(不包括n本身,因为这意味着不旋转),检查通过旋转每条线段k个单位后,是否所有线段都能在圆盘上找到对应的线段。

  4. 如何表示和比较线段:为了方便比较线段,我们可以为每条线段定义一个规范表示方法。例如,对于线段(ai, bi),我们可以假设ai < bi,这样每条线段都有唯一的表示(对于ai > bi的情况,可以将其转换为(bi, ai))。然后,将这些线段按照某种顺序排序存储,以便于比较。

  5. 实现细节:在实现时,我们需要注意如何高效地找到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,图形不旋转对称
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值