1 条题解

  • 0
    @ 2025-4-9 20:03:03

    题意分析

    本题围绕集合的置换(排列)概念展开。置换是集合到自身的一一映射,即对集合元素的一种重新排序方式。例如,对于集合{1, 2, 3, 4, 5},可以通过特定记录定义置换PP,如P(1)=4P(1)=4P(2)=1P(2)=1P(3)=5P(3)=5等。

    在此基础上,定义P1(n)=P(n)P^1(n)=P(n)Pk(n)=P(Pk1(n))P^k(n)=P(P^{k - 1}(n)),像P2(n)=P(P(n))P^2(n)=P(P(n))。存在一种特殊置换EN(n)E_N(n),它使集合中元素保持不变,且对于任意kk,有(EN)k=EN(E_N)^k = E_N

    重要结论是:对于NN个元素集合的任意置换P(n)P(n),存在自然数kk,使得Pk=ENP^k = E_N,满足此条件的最小自然数kk被称为置换PP的阶。

    题目要求编写程序,给定一个置换,求出其阶。输入时,第一行是集合元素个数NN1N10001\leq N\leq1000),第二行是NN个1到NN的自然数,代表置换P(1),P(2),,P(N)P(1), P(2), \cdots, P(N)。输出为该置换的阶,且答案不超过10910^9。如输入数据1中,N=5N = 5,置换为“4 1 5 2 3”,经计算其阶为6。

    解题思路

    1.本题的核心是求给定排列的阶数,即找到最小的自然数kk,使得Pk=ENP^k = E_N

    2.利用数论中排列可分解为不相交循环的性质。对于一个排列,每个元素都在某个循环节中。例如,若有排列PP,元素aa经过一系列变换P(a),P(P(a)),P(a), P(P(a)), \cdots最终回到aa,这就构成一个循环节。

    3.先通过遍历排列中的每个元素,标记未访问过的元素,从这些元素出发,沿着排列的映射关系找到对应的循环节,并记录循环节的长度。

    4.计算所有循环节长度的最小公倍数,这个最小公倍数就是排列的阶数。因为当每个循环节都回到初始状态时,整个排列就回到了恒等排列ENE_N

    分析

    1.时间复杂度:遍历每个元素找到其所在循环节的时间复杂度为O(N)O(N),其中NN是集合元素个数。计算循环节长度时,每个元素最多被访问一次。计算最小公倍数时,每次计算两个数的最小公倍数的时间复杂度近似为O(logN)O(\log N),总共最多计算NN次,所以计算最小公倍数的总时间复杂度也为O(N)O(N)。因此,整体时间复杂度为O(N)O(N)

    2.空间复杂度:使用一个布尔数组visited来标记元素是否被访问过,其大小为NN,用于存储循环节长度等的辅助变量空间复杂度较小,所以总的空间复杂度为O(N)O(N)

    实现步骤

    1.输入读取:从标准输入读取集合元素个数NN,以及排列PP的具体映射关系,即P(1),P(2),,P(N)P(1), P(2), \cdots, P(N),并存储在数组p中。

    2.初始化变量:定义一个布尔数组visited,初始化为false,用于标记元素是否已被访问;定义变量result,初始值为1,用于存储所有循环节长度的最小公倍数。

    3.寻找循环节并计算长度:遍历从11NN的每个元素,如果该元素未被访问过,则从该元素出发,沿着排列的映射关系不断访问下一个元素,同时标记已访问的元素,直到回到起始元素,记录这个循环节的长度。

    4.计算最小公倍数:对于每个找到的循环节长度,与当前的result计算最小公倍数,并更新result。

    5.输出结果:最后输出result,即排列的阶数。

    代码实现

    
    #include <iostream>
    using namespace std;
    
    // 求两个数的最大公约数
    int gcd(int a, int b) {
        while (b) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
    
    // 求两个数的最小公倍数
    int lcm(int a, int b) {
        return a * b / gcd(a, b);
    }
    
    int main() {
        int n;
        cin >> n;
        int p[1001];
        for (int i = 1; i <= n; i++) {
            cin >> p[i];
        }
        int result = 1;
        bool visited[1001] = {false};
        for (int i = 1; i <= n; i++) {
            if (!visited[i]) {
                int cycleLength = 0;
                int current = i;
                while (!visited[current]) {
                    visited[current] = true;
                    current = p[current];
                    cycleLength++;
                }
                result = lcm(result, cycleLength);
            }
        }
        cout << result << endl;
        return 0;
    }
    

    代码说明

    1.函数定义gcd函数采用辗转相除法计算两个数的最大公约数。通过不断用较小数对较大数取余,并更新两个数,直到余数为00,此时的除数就是最大公约数。 lcm函数利用最大公约数计算两个数的最小公倍数,公式为lcm(a,b)=a×b/gcd(a,b)lcm(a, b) = a \times b / gcd(a, b)

    2.主函数部分

    (1)首先读取输入的集合元素个数nn和排列pp

    (2)初始化result11visited数组全为false

    (3)通过外层for循环遍历每个元素,若元素未被访问过,则进入内层while循环寻找循环节。在循环节寻找过程中,标记访问过的元素并计算循环节长度cycleLength

    (4)找到一个循环节后,用lcm函数计算resultcycleLength的最小公倍数并更新result

    (5)最后输出result,即排列的阶数。

    • 1

    信息

    ID
    1370
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    12
    已通过
    1
    上传者