置换群的运算与应用

这是置换群运算应用的算法编程的研究学习。

一、概念名称的明析

图片原文叫法 代码里使用名称(修改后) 标准置换群叫法
加法对应关系:p+q→A[p+q][i]=A[p][A[q][i]] 置换加法 置换复合 Ap​∘Aq​
减法对应关系:p−q→A[p−q][A[q][i]]=A[p][i] 置换减法 置换逆复合
乘法对应关系:p∗k→A[k⋅p] 置换乘法(底层实现:置换快速幂) 置换的整数倍幂运算

✅ 关键约定(后面完整代码全部改成这套命名)

  1. 函数名:plus_perm → 置换加法(题目定义的加法,本质置换复合)
  2. 函数名:minus_perm → 置换减法(题目定义的减法,本质逆复合)
  3. 函数名:muti → 置换乘法(题目定义的乘整数 k,用置换快速幂实现)

二、运算法则

01
02
03

小结:

名称 记号 含义
置换复合 σ∘τ 先 τ,再 σ
置换逆复合 σ−1 σ 的逆置换,还原 σ 的操作
置换整数幂 σk k 次连续复合;负数幂 = 逆置换的幂

  置换复合 𝜎∘𝜏 的‌主流规定是从右往左执行‌,即先施加右边的 𝜏,再施加左边的 𝜎。这和函数复合的记法保持一致:σ∘τ 表示先算 τ,再算 σ,也就是 σ(τ(x))。‌‌

三、C++ 置换运算模板(术语严格对齐:置换复合、置换逆复合、置换整数幂)

实现:
mul(σ,τ) 置换复合
inv(σ) 求逆置换
pow_perm(σ,k) 置换整数幂(支持正负 k,快速幂)。

约定:perm 用数组表示置换,p[i] 代表元素 i 经过置换映射到 p[i]
置换复合 res = σ∘τ:先执行 τ,再执行 σ,满足 res[i] = σ[ τ[i] ],和上面数学定义完全一致
下标:元素编号从 1 ~ n(竞赛常用,方便 CSP/NOIP)

测试1:

#include <iostream>
#include <vector>
using namespace std;
using perm = vector<int>; // perm 代表一个置换

// ===================== 置换复合 σ∘τ  先τ,后σ =====================
perm perm_compose(const perm &sigma, const perm &tau)
{
    int n = sigma.size() - 1;
    perm res(n + 1);
    for(int i = 1; i <= n; ++i)
    {
        res[i] = sigma[ tau[i] ];
    }
    return res;
}

// ===================== 置换逆复合:求σ的逆置换 σ⁻¹ =====================
perm perm_inverse(const perm &sigma)
{
    int n = sigma.size() - 1;
    perm res(n + 1);
    for(int i = 1; i <= n; ++i)
    {
        // sigma[i] = j  ⇒ 逆置换满足 res[j] = i
        int j = sigma[i];
        res[j] = i;
    }
    return res;
}

// ===================== 置换整数幂 σ^k (支持k为正、负、0,置换快速幂) =====================
perm perm_pow(perm sigma, long long k)
{
    int n = sigma.size() - 1;
    // 初始化结果:恒等置换 ε,ε[i]=i
    perm res(n + 1);
    for(int i = 1; i <= n; ++i) res[i] = i;

    if(k < 0)
    {
        sigma = perm_inverse(sigma); // k负数,先取逆置换
        k = -k;
    }

    // 快速幂核心:不断复合
    while(k > 0)
    {
        if(k % 2 == 1)
        {
            res = perm_compose(res, sigma);
        }
        sigma = perm_compose(sigma, sigma);
        k /= 2;
    }
    return res;
}

// ========== 辅助函数:打印置换 ==========
void print_perm(const perm &p)
{
    int n = p.size() - 1;
    cout << "置换:";
    for(int i = 1; i <= n; i++) cout << p[i] << " ";
    cout << endl;
}

int main()
{
    // 测试示例 n=3
    perm sigma = {0, 3, 2, 1}; // sigma(1)=3, sigma(2)=2, sigma(3)=1
    perm tau   = {0, 2, 3, 1}; // tau(1)=2,  tau(2)=3,  tau(3)=1

    cout << "σ:"; print_perm(sigma);
    cout << "τ:"; print_perm(tau);

    perm comp = perm_compose(sigma, tau);
    cout << "置换复合 σ∘τ:"; print_perm(comp);

    perm inv_sigma = perm_inverse(sigma);
    cout << "置换逆复合 σ⁻¹:"; print_perm(inv_sigma);

    perm pow2 = perm_pow(sigma, 2);
    cout << "σ^2:"; print_perm(pow2);

    perm pow_neg1 = perm_pow(sigma, -1);
    cout << "σ^(-1):"; print_perm(pow_neg1);

    return 0;
}
运行输出示例:
σ:置换:3 2 1 
τ:置换:2 3 1 
置换复合 σ∘τ:2 1 3 
置换逆复合 σ⁻¹:3 2 1 
σ^2:置换:1 2 3 
σ^(-1):置换:3 2 1 

测试2:

#include <iostream>
#include <vector>
using namespace std;

// 类型别名:perm 代表一个置换
// perm[i] 表示:位置 i 经过置换后,映射到 perm[i]
using perm = vector<int>;

// ========= 1. 置换复合 res = f ∘ g  先g,后f =========
perm compose(const perm &f, const perm &g)
{
    int n = f.size();           // 获取置换的元素个数
    perm res(n);                // 新建存储结果置换的数组
    for(int i = 0; i < n; i++)
    {
        res[i] = f[ g[i] ];     // i先被g映射到g[i],再被f映射到f[g[i]]
    }
    return res;
}

// ========= 2. 置换逆复合:求置换 p 的逆置换 =========
perm inverse_perm(const perm &p)
{
    int n = p.size();
    perm inv(n);
    for(int i = 0; i < n; i++)
    {
        inv[ p[i] ] = i;        // 如果 p[i]=j,则逆置换满足 inv[j]=i
    }
    return inv;
}

// ========= 3. 置换整数幂 p^k,k可以是正、负、0 =========
perm perm_pow(perm p, long long k)
{
    int n = p.size();
    perm res(n);
    // 初始化结果为【恒等置换】,作为乘法单位元
    for(int i = 0; i < n; i++) res[i] = i;

    if(k < 0)
    {
        p = inverse_perm(p);   // 负数幂:先取逆置换
        k = -k;
    }

    // 快速幂(二进制快速幂,置换复合当作乘法)
    while(k > 0)
    {
        if(k % 2 == 1)
        {
            res = compose(res, p);
        }
        p = compose(p, p);
        k /= 2;
    }
    return res;
}

// 辅助函数:打印置换
void print_perm(const perm &p)
{
    for(auto v : p) cout << v << " ";
    cout << endl;
}

int main()
{
    // 测试:4元置换,下标0,1,2,3
    perm f = {1,2,3,0};
    perm g = {3,0,1,2};

    cout << "置换f: "; print_perm(f);
    cout << "置换g: "; print_perm(g);

    perm fg = compose(f,g);
    cout << "复合 f∘g : "; print_perm(fg);

    perm inv_f = inverse_perm(f);
    cout << "f的逆置换 : "; print_perm(inv_f);

    perm f3 = perm_pow(f, 3);
    cout << "f^3 : "; print_perm(f3);

    perm f_neg2 = perm_pow(f, -2);
    cout << "f^(-2): "; print_perm(f_neg2);

    return 0;
}
运行输出示例:
置换f: 1 2 3 0 
置换g: 3 0 1 2 
复合 f∘g : 0 1 2 3 
f的逆置换 : 3 0 1 2 
f^3 : 3 0 1 2 
f^(-2): 2 3 0 1 

四、数学问题模型:求整数 x 满足 (a^x = b)(置换方程,对应 a 的幂等于置换 b),用不定方程 + 扩展欧几里得求解,遍历通解

1、 plus 置换复合 + minus 置换逆复合 + 置换快速幂 muti

#include <iostream>
#include <cstring>
using namespace std;

const int MAXN = 1005;
using anstype = int[MAXN];

/**
 * @brief plus 置换加法:temp[i] = a1[ a2[i] ]
 * 对应原题:p+q 的密码 = plus(A[p],A[q])
 * c = a1 ◦ a2 ,先做a2置换,再做a1置换
 */
void plus_perm(anstype a1, anstype a2, anstype c, int n)
{
    for(int i = 1; i <= n; i++)
    {
        c[i] = a1[ a2[i] ];
    }
}

/**
 * @brief minus 置换减法:temp[ a2[i] ] = a1[i]
 * 原题定义:A[p−q][ A[q][i] ] = A[p][i]
 * 输入a1=A[p], a2=A[q],输出A[p−q]
 */
void minus_perm(anstype a1, anstype a2, anstype c, int n)
{
    memset(c, 0, sizeof(anstype));
    for(int i = 1; i <= n; i++)
    {
        c[ a2[i] ] = a1[i];
    }
}

/**
 * @brief muti 置换快速幂:t * a ,t倍置换(二进制倍增)
 * 输入置换a,整数t,返回 t*a 对应的置换
 */
void muti(anstype a, long long t, anstype res, int n)
{
    // 初始res是恒等置换,也就是0时刻密码 A[0][i]=i
    for(int i = 1; i <= n; i++)
        res[i] = i;

    anstype base;
    memcpy(base, a, sizeof(anstype));

    while(t > 0)
    {
        if(t & 1) // t当前二进制位为1,把base合并进res
        {
            anstype tmp;
            plus_perm(res, base, tmp, n);
            memcpy(res, tmp, sizeof(anstype));
        }
        // base = base + base,等价 ×2
        anstype tmp;
        plus_perm(base, base, tmp, n);
        memcpy(base, tmp, sizeof(anstype));
        t >>= 1;
    }
}

int main()
{
    // 样例测试,对应题目例子 A[3][i]=(1,2,3,5,4)
    int n = 5;
    anstype a = {0, 1, 2, 3, 5, 4};
    anstype res;

    muti(a, 3, res, n);
    cout << "置换快速幂 t=3 结果:";
    for(int i = 1; i <= n; i++)
    {
        cout << res[i] << " ";
    }
    cout << endl;

    // 测试minus减法示例
    anstype a_p;   // A[p]
    anstype a_q;   // A[q]
    anstype a_pq;  // A[p−q]
    // 示例:p=3,q=1,p-q=2
    for(int i=1;i<=5;i++) a_p[i] = res[i];
    a_q[1]=1,a_q[2]=2,a_q[3]=3,a_q[4]=5,a_q[5]=4;
    minus_perm(a_p,a_q,a_pq,n);
    cout << "minus减法 A[3-1]=A[2]:";
    for(int i = 1; i <= n; i++)
    {
        cout << a_pq[i] << " ";
    }
    return 0;
}

函数对照说明

  1. plus_perm:对应 Pascal 的plus,置换复合,计算 p+q
  2. minus_perm:对应 Pascal 的minus,求 p−q 的置换
  3. muti:置换快速幂,计算 t * p,二进制倍增,复杂度 (O(n\log t))

2、扩展欧几里得

题目后半段需要解不定方程 (x\cdot t_1 + y\cdot t_2 = k),用扩展欧几里得算出 x,y,
然后 ans = plus( muti(A[t1],x), muti(A[t2],y) ) 得到 k 时刻密码。

  1. 置换复合 plus_perm(p+q)
  2. 置换减法 minus_perm(p−q)
  3. 置换快速幂 muti(t 倍置换)
  4. 扩展欧几里得算法,求解不定方程 (x\cdot t_1+y\cdot t_2 =k)
  5. 主逻辑:贝祖定理判断有无解,算出 x,y 后合并置换得到 k 时刻密码
#include <iostream>
#include <cstring>
using namespace std;

const int MAXN = 1005;
using anstype = int[MAXN];

// ========== 置换运算部分 ==========
/**
 * plus置换复合:c = a1 ◦ a2,c[i] = a1[a2[i]],对应 p+q
 */
void plus_perm(anstype a1, anstype a2, anstype c, int n)
{
    for (int i = 1; i <= n; i++)
    {
        c[i] = a1[a2[i]];
    }
}

/**
 * minus置换减法:已知A[p]、A[q],求A[p−q]
 * 公式:A[p−q][A[q][i]] = A[p][i]
 */
void minus_perm(anstype a1, anstype a2, anstype c, int n)
{
    memset(c, 0, sizeof(anstype));
    for (int i = 1; i <= n; i++)
    {
        c[a2[i]] = a1[i];
    }
}

/**
 * 置换快速幂:res = t * a,t倍置换
 */
void muti(anstype a, long long t, anstype res, int n)
{
    // 初始:恒等置换 A[0]
    for (int i = 1; i <= n; i++)
        res[i] = i;

    anstype base;
    memcpy(base, a, sizeof(anstype));

    while (t > 0)
    {
        if (t & 1)
        {
            anstype tmp;
            plus_perm(res, base, tmp, n);
            memcpy(res, tmp, sizeof(anstype));
        }
        anstype tmp;
        plus_perm(base, base, tmp, n);
        memcpy(base, tmp, sizeof(anstype));
        t >>= 1;
    }
}

// ========== 扩展欧几里得(数论部分) ==========
// ax + by = gcd(a,b)
long long exgcd(long long a, long long b, long long &x, long long &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    long long d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

/**
 * 求解不定方程 a*x + b*y = k
 * 返回true表示有解,x,y为一组特解
 */
bool solve_equation(long long a, long long b, long long k, long long &x, long long &y)
{
    long long d = exgcd(a, b, x, y);
    if (k % d != 0)
    {
        return false; // 无解,k不能被gcd(a,b)整除
    }
    // 特解放大,得到 a*x + b*y =k
    long long mul = k / d;
    x *= mul;
    y *= mul;
    return true;
}

// ========== 合并两个置换:x*A[t1] + y*A[t2] ==========
void combine(anstype A_t1, anstype A_t2, long long x, long long y, anstype ans, int n)
{
    anstype p1, p2;
    muti(A_t1, x, p1, n);  // p1 = x倍的A[t1]
    muti(A_t2, y, p2, n);  // p2 = y倍的A[t2]
    // ans = p1 + p2
    plus_perm(p1, p2, ans, n);
}

int main()
{
    // ===== 测试样例,和题目例子保持一致 =====
    int n = 5;
    // A[1] = {0,1,2,3,5,4}  下标0不用
    anstype A_t1 = {0, 1, 2, 3, 5, 4};
    long long t1 = 1;
    long long t2 = 2;
    long long k = 3;

    long long x, y;
    bool ok = solve_equation(t1, t2, k, x, y);
    if (!ok)
    {
        cout << "无解!k不能被gcd(t1,t2)整除" << endl;
        return 0;
    }
    cout << "方程 " << t1 << "*x + " << t2 << "*y = " << k << " 的一组特解:" << endl;
    cout << "x = " << x << " , y = " << y << endl;

    // A[t2] = A[2],由A[1]做置换快速幂 t=2得到
    anstype A_t2;
    muti(A_t1, t2, A_t2, n);

    // 合并得到k时刻密码
    anstype ans_perm;
    combine(A_t1, A_t2, x, y, ans_perm, n);

    cout << "k=" << k << "时刻密码序列:";
    for (int i = 1; i <= n; i++)
    {
        cout << ans_perm[i] << " ";
    }
    cout << endl;

    return 0;
}

说明

  1. exgcd:算出 (t_1 x + t_2 y =k) 的整数解 x,y;
    • 无解条件:k % gcd(t1,t2) != 0,直接输出无解
  2. muti(A_t1, x, p1):计算 (x\cdot t_1) 对应的置换;
    • 注意:x 可以是负数!当前代码还没处理负数 x/y

3、支持正负倍数置换 + plus/minus + 置换快速幂 + exgcd

重点新增:处理 x,y 为负数的情况。
原理:
若倍数 s < 0:s * A 等价于 减去 abs(s)*A,也就是求置换的逆置换。
置换P的逆置换invP满足:plus(P, invP) = 恒等置换。
逆置换正好用我们已有的minus_perm:invP = minus(恒等置换, P)

#include <iostream>
#include <cstring>
using namespace std;

const int MAXN = 1005;
using anstype = int[MAXN];

// ========== 置换基础运算 ==========
/**
 * plus置换复合:c = a1 ◦ a2,c[i] = a1[a2[i]],对应 p+q
 */
void plus_perm(anstype a1, anstype a2, anstype c, int n)
{
    for (int i = 1; i <= n; i++)
    {
        c[i] = a1[a2[i]];
    }
}

/**
 * minus置换减法:已知A[p]、A[q],求A[p−q]
 * 公式:A[p−q][A[q][i]] = A[p][i]
 */
void minus_perm(anstype a1, anstype a2, anstype c, int n)
{
    memset(c, 0, sizeof(anstype));
    for (int i = 1; i <= n; i++)
    {
        c[a2[i]] = a1[i];
    }
}

/**
 * 置换快速幂:res = t * a,t倍置换(t >= 0)
 */
void muti_pos(anstype a, long long t, anstype res, int n)
{
    // 初始:恒等置换 A[0]
    for (int i = 1; i <= n; i++)
        res[i] = i;

    anstype base;
    memcpy(base, a, sizeof(anstype));

    while (t > 0)
    {
        if (t & 1)
        {
            anstype tmp;
            plus_perm(res, base, tmp, n);
            memcpy(res, tmp, sizeof(anstype));
        }
        anstype tmp;
        plus_perm(base, base, tmp, n);
        memcpy(base, tmp, sizeof(anstype));
        t >>= 1;
    }
}

/**
 * 通用倍数置换:s * a,支持s为【正数、负数、0】
 * s<0:先求a的逆置换,再计算 abs(s) 倍
 */
void muti(anstype a, long long s, anstype res, int n)
{
    if (s == 0)
    {
        // 0倍:恒等置换
        for (int i = 1; i <= n; i++) res[i] = i;
        return;
    }
    if (s > 0)
    {
        muti_pos(a, s, res, n);
        return;
    }
    // s < 0,求逆置换
    long long abs_s = -s;
    anstype identity;
    for (int i = 1; i <= n; i++) identity[i] = i;
    anstype inv_a;
    // inv_a = identity - a  即A[0 - 1] = A[-1],a的逆置换
    minus_perm(identity, a, inv_a, n);
    muti_pos(inv_a, abs_s, res, n);
}

// ========== 扩展欧几里得(数论部分) ==========
long long exgcd(long long a, long long b, long long &x, long long &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    long long d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

/**
 * 求解不定方程 a*x + b*y = k
 * 返回true表示有解,x,y为一组特解
 */
bool solve_equation(long long a, long long b, long long k, long long &x, long long &y)
{
    long long d = exgcd(a, b, x, y);
    if (k % d != 0)
    {
        return false; // 无解
    }
    long long mul = k / d;
    x *= mul;
    y *= mul;
    return true;
}

// ========== 合并置换 x*A_t1 + y*A_t2 ==========
void combine(anstype A_t1, anstype A_t2, long long x, long long y, anstype ans, int n)
{
    anstype p1, p2;
    muti(A_t1, x, p1, n);  // x * A[t1] (自动处理正负x)
    muti(A_t2, y, p2, n);  // y * A[t2] (自动处理正负y)
    plus_perm(p1, p2, ans, n);
}

int main()
{
    // ===== 测试样例,和题目例子保持一致 =====
    int n = 5;
    // A[1] = {0,1,2,3,5,4} 下标0弃用
    anstype A_t1 = {0, 1, 2, 3, 5, 4};
    long long t1 = 1;
    long long t2 = 2;
    long long k = 3;

    long long x, y;
    bool ok = solve_equation(t1, t2, k, x, y);
    if (!ok)
    {
        cout << "无解!k不能被gcd(t1,t2)整除" << endl;
        return 0;
    }
    cout << "方程 " << t1 << "*x + " << t2 << "*y = " << k << " 的一组特解:" << endl;
    cout << "x = " << x << " , y = " << y << endl;

    // 算出A[t2] = A[2]
    anstype A_t2;
    muti(A_t1, t2, A_t2, n);

    // 合并得到k时刻密码
    anstype ans_perm;
    combine(A_t1, A_t2, x, y, ans_perm, n);

    cout << "k=" << k << "时刻密码序列:";
    for (int i = 1; i <= n; i++)
    {
        cout << ans_perm[i] << " ";
    }
    cout << endl;

    // 额外测试:负数倍数示例
    cout << "\n==== 测试负数倍数(A[-1],A1的逆置换) ====" << endl;
    anstype inv_A1;
    muti(A_t1, -1, inv_A1, n);
    cout << "A[-1] 逆置换序列:";
    for(int i=1;i<=n;i++) cout << inv_A1[i] << " ";
    cout << endl;

    return 0;
}

说明

  1. 拆分了muti_pos(只处理≥0 倍数)和通用muti函数
  2. 当s<0:
    • 用minus_perm(恒等置换, a, inv_a, n)求出逆置换
    • 再对逆置换做正数倍的快速幂
  3. combine函数直接调用新版muti,不管 x,y 正负都可以正常计算

对应原题:

  • (A_{p+q}=A_p \circ A_q) 对应plus_perm
  • (A_{p-q}=A_p - A_q) 对应minus_perm
  • (A_{s\cdot t}):s 正数直接倍增;s 负数,先求逆置换再倍增
  • 贝祖定理:(t_1 x + t_2 y =k),不能整除则无解;
  • 算出 x,y 之后,合并两个置换 $A_{x\cdot t_1 + y\cdot t_2}=A_{x t_1} \circ A_{y t_2}$

可选扩展(竞赛常考):

exgcd 得到的 x,y 只是一组特解,有无穷多组整数解:

1

不同的 x',y',算出来的最终置换是完全一样的(置换群性质)。

竞赛提交完整版 C++ 代码

  • 支持标准输入读取:n、t1、t2、k,再读入 A [t1]、A [t2] 置换数组
  • 内置:置换复合 plus_perm、置换减法 minus_perm、正负通用置换快速幂、扩展欧几里得 exgcd
  • 输入格式在代码注释写明,可直接在 OJ 提交
#include <iostream>
#include <cstring>
using namespace std;

const int MAXN = 1005;
using anstype = int[MAXN];

// ========== 置换基础运算 ==========
/**
 * plus置换复合:c = a1 ◦ a2,c[i] = a1[a2[i]],对应 p+q
 */
void plus_perm(anstype a1, anstype a2, anstype c, int n)
{
    for (int i = 1; i <= n; i++)
    {
        c[i] = a1[a2[i]];
    }
}

/**
 * minus置换减法:已知A[p]、A[q],求A[p−q]
 * 公式:A[p−q][A[q][i]] = A[p][i]
 */
void minus_perm(anstype a1, anstype a2, anstype c, int n)
{
    memset(c, 0, sizeof(anstype));
    for (int i = 1; i <= n; i++)
    {
        c[a2[i]] = a1[i];
    }
}

/**
 * 置换快速幂:res = t * a,t >= 0
 */
void muti_pos(anstype a, long long t, anstype res, int n)
{
    // 初始:恒等置换 A[0]
    for (int i = 1; i <= n; i++)
        res[i] = i;

    anstype base;
    memcpy(base, a, sizeof(anstype));

    while (t > 0)
    {
        if (t & 1)
        {
            anstype tmp;
            plus_perm(res, base, tmp, n);
            memcpy(res, tmp, sizeof(anstype));
        }
        anstype tmp;
        plus_perm(base, base, tmp, n);
        memcpy(base, tmp, sizeof(anstype));
        t >>= 1;
    }
}

/**
 * 通用倍数置换:s * a,支持s正数、负数、0
 */
void muti(anstype a, long long s, anstype res, int n)
{
    if (s == 0)
    {
        for (int i = 1; i <= n; i++) res[i] = i;
        return;
    }
    if (s > 0)
    {
        muti_pos(a, s, res, n);
        return;
    }
    // s < 0,求逆置换
    long long abs_s = -s;
    anstype identity;
    for (int i = 1; i <= n; i++) identity[i] = i;
    anstype inv_a;
    minus_perm(identity, a, inv_a, n);
    muti_pos(inv_a, abs_s, res, n);
}

// ========== 扩展欧几里得 ==========
long long exgcd(long long a, long long b, long long &x, long long &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    long long d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

/**
 * 解方程 a*x + b*y = k
 * 返回true有解,x,y为一组特解
 */
bool solve_equation(long long a, long long b, long long k, long long &x, long long &y)
{
    long long d = exgcd(a, b, x, y);
    if (k % d != 0)
    {
        return false;
    }
    long long mul = k / d;
    x *= mul;
    y *= mul;
    return true;
}

// ========== 合并置换 x*A_t1 + y*A_t2 ==========
void combine(anstype A_t1, anstype A_t2, long long x, long long y, anstype ans, int n)
{
    anstype p1, p2;
    muti(A_t1, x, p1, n);
    muti(A_t2, y, p2, n);
    plus_perm(p1, p2, ans, n);
}

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

    int n;
    long long t1, t2, k;
    cin >> n >> t1 >> t2 >> k;

    anstype A_t1, A_t2;
    // 读入A[t1]置换,下标1~n
    for (int i = 1; i <= n; i++)
    {
        cin >> A_t1[i];
    }
    // 读入A[t2]置换
    for (int i = 1; i <= n; i++)
    {
        cin >> A_t2[i];
    }

    long long x, y;
    bool ok = solve_equation(t1, t2, k, x, y);
    if (!ok)
    {
        cout << "NO" << endl;
        return 0;
    }

    anstype ans_perm;
    combine(A_t1, A_t2, x, y, ans_perm, n);

    cout << "YES\n";
    for (int i = 1; i <= n; i++)
    {
        cout << ans_perm[i] << " ";
    }
    cout << endl;

    return 0;
}

输入样例(对应原题例子)

5 1 2 3
1 2 3 5 4
1 2 3 5 4

输入说明:

  • 第一行:n t1 t2 k → 5 1 2 3
  • 第二行:A [t1] = A [1] 的 n 个数字
  • 第三行:A [t2] = A [2] 的 n 个数字

输出样例:

YES
1 2 3 5 4

竞赛规则说明

  1. 无解输出NO;有解输出YES,后面输出置换序列。
  2. 变量全部用long long,防止 t1,t2,k 很大溢出。
  3. ios::sync_with_stdio(false); cin.tie(nullptr);加速 cin 输入,OJ 必备。
#include <iostream>
#include <cstring>
using namespace std;

// 置换数组最大长度,根据题目需求修改
const int MAXN = 1005;
// 定义置换类型anstype,数组下标从1开始使用,下标0闲置
using anstype = int[MAXN];

/**
 * @brief plus_perm 【置换加法】,对应图片:加法对应关系 p+q
 * 题目定义:A[p+q][i] = A[p][ A[q][i] ]
 * 标准数学:置换复合运算,先执行A[q],再执行A[p]
 * @param a1 A[p]
 * @param a2 A[q]
 * @param c 输出 A[p+q]
 * @param n 置换长度
 */
void plus_perm(anstype a1, anstype a2, anstype c, int n)
{
    // 遍历1~n,计算置换加法
    for (int i = 1; i <= n; i++)
    {
        c[i] = a1[a2[i]];
    }
}

/**
 * @brief minus_perm 【置换减法】,对应图片:减法对应关系 p−q
 * 题目定义:A[p−q][ A[q][i] ] = A[p][i] (P≥Q)
 * 标准数学:置换逆复合,已知A[p]、A[q],反求A[p−q]
 * @param a1 A[p]
 * @param a2 A[q]
 * @param c 输出 A[p−q]
 * @param n 置换长度
 */
void minus_perm(anstype a1, anstype a2, anstype c, int n)
{
    // 清空数组
    memset(c, 0, sizeof(anstype));
    for (int i = 1; i <= n; i++)
    {
        c[a2[i]] = a1[i];
    }
}

/**
 * @brief muti_pos 【非负的置换乘法】,对应图片:乘法对应关系 p*k,k≥0
 * 题目说明:用二进制迭代,将计算次数由O(k)降为O(logk),也就是置换快速幂
 * @param a 基础置换 A[p]
 * @param t 倍数k,>=0
 * @param res 输出结果置换 A[p*k]
 * @param n 置换长度
 */
void muti_pos(anstype a, long long t, anstype res, int n)
{
    // A[0]是0时刻的密码序列(恒等置换:A[0][i]=i),置换乘0等于不变
    for (int i = 1; i <= n; i++)
        res[i] = i;

    // base保存当前基础置换副本
    anstype base;
    memcpy(base, a, sizeof(anstype));

    // 置换快速幂主循环(二进制迭代)
    while (t > 0)
    {
        // 如果当前二进制最低位是1,把base通过置换加法合并进答案res
        if (t & 1)
        {
            anstype tmp;
            plus_perm(res, base, tmp, n);
            memcpy(res, tmp, sizeof(anstype));
        }
        // base = base 置换加法 base,等价 ×2倍(p*2)
        anstype tmp;
        plus_perm(base, base, tmp, n);
        memcpy(base, tmp, sizeof(anstype));
        // t右移一位,处理下一个二进制位
        t >>= 1;
    }
}

/**
 * @brief muti 【通用置换乘法】,对应图片乘法对应关系,支持k>0、k=0、k<0
 * k<0:等价先求原置换的逆,再乘abs(k)
 * @param a 基础置换A[p]
 * @param s 倍数k,可以是负数
 * @param res 输出置换 A[p*s]
 * @param n 置换长度
 */
void muti(anstype a, long long s, anstype res, int n)
{
    // s=0:返回0时刻密码(恒等置换A[0])
    if (s == 0)
    {
        for (int i = 1; i <= n; i++) res[i] = i;
        return;
    }
    // s>0,直接调用非负置换乘法(置换快速幂)
    if (s > 0)
    {
        muti_pos(a, s, res, n);
        return;
    }
    // s < 0,求逆置换
    long long abs_s = -s;
    anstype identity;
    // identity是0时刻密码序列(恒等置换A[0])
    for (int i = 1; i <= n; i++) identity[i] = i;
    anstype inv_a;
    // inv_a = identity 置换减法 a,得到A[-p],即A[p]的逆置换
    minus_perm(identity, a, inv_a, n);
    // 对逆置换做abs_s倍的置换乘法
    muti_pos(inv_a, abs_s, res, n);
}

/**
 * @brief exgcd 扩展欧几里得算法
 * 求解 ax + by = gcd(a,b)
 * @param a 整数a
 * @param b 整数b
 * @param x 输出解x
 * @param y 输出解y
 * @return gcd(a,b)最大公约数
 */
long long exgcd(long long a, long long b, long long &x, long long &y)
{
    // 递归终止条件,b=0,gcd(a,0)=a,x=1,y=0
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    // 递归求解 exgcd(b, a mod b)
    long long d = exgcd(b, a % b, y, x);
    // 回溯更新x,y
    y -= a / b * x;
    return d;
}

/**
 * @brief solve_equation 求解不定方程 t1*x + t2*y = k
 * @param a 系数t1
 * @param b 系数t2
 * @param k 目标值k
 * @param x 输出一组特解x
 * @param y 输出一组特解y
 * @return true有解;false无解
 */
bool solve_equation(long long a, long long b, long long k, long long &x, long long &y)
{
    long long d = exgcd(a, b, x, y);
    // 贝祖定理:k不能被d整除,方程无整数解
    if (k % d != 0)
    {
        return false;
    }
    // 将特解放大,得到 a*x + b*y =k
    long long mul = k / d;
    x *= mul;
    y *= mul;
    return true;
}

/**
 * @brief combine 合并:A[x*t1 + y*t2] = A[x*t1] 置换加法 A[y*t2]
 * @param A_t1 t1对应的密码置换
 * @param A_t2 t2对应的密码置换
 * @param x 系数x
 * @param y 系数y
 * @param ans 输出合并后的k时刻密码置换
 * @param n 置换长度
 */
void combine(anstype A_t1, anstype A_t2, long long x, long long y, anstype ans, int n)
{
    anstype p1, p2;
    muti(A_t1, x, p1, n);  // p1 = x倍A[t1],置换乘法
    muti(A_t2, y, p2, n);  // p2 = y倍A[t2],置换乘法
    plus_perm(p1, p2, ans, n); // 置换加法合并两个结果
}

/**
 * @brief equal_perm 判断两个密码置换序列是否完全相同,用于通解校验
 * @param p1 置换1
 * @param p2 置换2
 * @param n 长度
 * @return true相同
 */
bool equal_perm(anstype p1, anstype p2, int n)
{
    for(int i=1;i<=n;i++)
    {
        if(p1[i] != p2[i]) return false;
    }
    return true;
}

int main()
{
    // 关闭cin同步,加速输入输出,OJ必备
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    long long t1, t2, k;
    // 读入n、t1、t2、k
    cin >> n >> t1 >> t2 >> k;

    anstype A_t1, A_t2;
    // 读入t1时刻的密码序列A[t1]
    for (int i = 1; i <= n; i++)
    {
        cin >> A_t1[i];
    }
    // 读入t2时刻的密码序列A[t2]
    for (int i = 1; i <= n; i++)
    {
        cin >> A_t2[i];
    }

    long long x0, y0;
    // 求解不定方程 t1*x + t2*y =k
    bool ok = solve_equation(t1, t2, k, x0, y0);
    if (!ok)
    {
        cout << "NO" << endl;
        return 0;
    }
    cout << "YES" << endl;
    // 输出基础特解
    cout << "基础特解 x0 = " << x0 << " , y0 = " << y0 << "\n";

    // ========== 通解公式(由扩展欧几里得得到) ==========
    // d = gcd(t1,t2)
    long long d = exgcd(t1,t2,x0,y0);
    // 通解:
    // x = x0 + (t2/d) * k0
    // y = y0 - (t1/d) * k0
    long long dx = t2 / d;
    long long dy = t1 / d;

    // 用基础特解算出基准答案置换
    anstype base_ans;
    combine(A_t1, A_t2, x0, y0, base_ans, n);
    cout << "基准k时刻密码序列:";
    for(int i=1;i<=n;i++)
    {
        cout << base_ans[i] << " ";
    }
    cout << "\n\n==== 遍历多组通解,验证结果不变 ====\n";

    // 遍历k0,取 -2,-1,0,1,2 五组通解验证
    for(long long k0 = -2; k0 <= 2; k0++)
    {
        long long x = x0 + dx * k0;
        long long y = y0 - dy * k0;
        anstype cur_ans;
        combine(A_t1, A_t2, x, y, cur_ans, n);
        bool same = equal_perm(base_ans, cur_ans, n);
        cout << "k0="<<k0<<" : x="<<x<<" , y="<<y<<"  结果是否一致:"<<(same?"是":"否")<<endl;
    }

    return 0;
}

测试输入样例

5 1 2 3
1 2 3 5 4
1 2 3 5 4

输出示例

YES
基础特解 x0 = 3 , y0 = 0
基准k时刻密码序列:1 2 3 5 4 

==== 遍历多组通解,验证结果不变 ====
k0=-2 : x=-1 , y=2  结果是否一致:是
k0=-1 : x=1 , y=1  结果是否一致:是
k0=0 : x=3 , y=0  结果是否一致:是
k0=1 : x=5 , y=-1  结果是否一致:是
k0=2 : x=7 , y=-2  结果是否一致:是

可以看到:不同的 x,y(通解),计算出来的 k 时刻密码序列完全一样,和题目置换群的性质吻合。

4、完整合并代码:置换运算 + 扩展欧几里得 + 不定方程特解、通解遍历

术语约定:

  1. compose:置换复合 (f\circ g),先 g 后 f
  2. inverse_perm:置换逆复合,求 (p^{-1})
  3. perm_pow:置换整数幂 (p^k)(k 正负 0 都支持)
  4. 数学问题模型:求整数 x 满足 (a^x = b)(置换方程,对应 a 的幂等于置换 b),用不定方程 + 扩展欧几里得求解,遍历通解
#include <iostream>
#include <vector>
using namespace std;

using perm = vector<int>;

// ========= 1. 置换复合 res = f ∘ g  先g,后f =========
perm compose(const perm &f, const perm &g)
{
    int n = f.size();           // 获取置换的元素个数
    perm res(n);                // 新建存储结果置换的数组
    for(int i = 0; i < n; i++)
    {
        res[i] = f[ g[i] ];     // i先被g映射到g[i],再被f映射到f[g[i]]
    }
    return res;
}

// ========= 2. 置换逆复合:求置换 p 的逆置换 =========
perm inverse_perm(const perm &p)
{
    int n = p.size();
    perm inv(n);
    for(int i = 0; i < n; i++)
    {
        inv[ p[i] ] = i;        // 如果 p[i]=j,则逆置换满足 inv[j]=i
    }
    return inv;
}

// ========= 3. 置换整数幂 p^k,k可以是正、负、0 =========
perm perm_pow(perm p, long long k)
{
    int n = p.size();
    perm res(n);
    // 初始化结果为【恒等置换】,作为乘法单位元
    for(int i = 0; i < n; i++) res[i] = i;

    if(k < 0)
    {
        p = inverse_perm(p);   // 负数幂:先取逆置换
        k = -k;
    }

    // 二进制快速幂(置换复合当作乘法)
    while(k > 0)
    {
        if(k % 2 == 1)
        {
            res = compose(res, p);
        }
        p = compose(p, p);
        k /= 2;
    }
    return res;
}

// ========= 辅助函数:打印置换 =========
void print_perm(const perm &p)
{
    for(auto v : p) cout << v << " ";
    cout << endl;
}

// ========= 扩展欧几里得算法,求解 ax+by=gcd(a,b) =========
// 返回gcd,x,y传出一组特解
long long exgcd(long long a, long long b, long long &x, long long &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    long long g = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return g;
}

/*
    求解不定方程 a*x + b*y = c
    返回值:是否有解 true/false
    x0,y0 输出一组特解
    g 输出gcd(a,b)
*/
bool solve_equation(long long a, long long b, long long c, long long &x0, long long &y0, long long &g)
{
    g = exgcd(a, b, x0, y0);
    if (c % g != 0)
    {
        return false; // 无整数解
    }
    // 把特解放大,得到 ax+by=c 的特解
    x0 = x0 * (c / g);
    y0 = y0 * (c / g);
    return true;
}

int main()
{
    // ===== 测试置换部分 =====
    cout << "===== 置换运算测试 =====" << endl;
    perm f = {1,2,3,0};
    perm g = {3,0,1,2};

    cout << "置换f: "; print_perm(f);
    cout << "置换g: "; print_perm(g);

    perm fg = compose(f,g);
    cout << "复合 f∘g : "; print_perm(fg);

    perm inv_f = inverse_perm(f);
    cout << "f的逆置换 : "; print_perm(inv_f);

    perm f3 = perm_pow(f, 3);
    cout << "f^3 : "; print_perm(f3);

    perm f_neg2 = perm_pow(f, -2);
    cout << "f^(-2): "; print_perm(f_neg2);

    // ===== 不定方程:求通解并遍历若干组解 =====
    cout << "\n===== 不定方程求解 + 通解遍历 =====" << endl;
    long long a,b,c;
    // 示例方程:2x + 4y = 8
    a = 2; b = 4; c = 8;
    long long x0,y0,g;
    bool has_sol = solve_equation(a,b,c,x0,y0,g);
    if (!has_sol)
    {
        cout << "方程 " << a << "x + " << b << "y = " << c << " 无整数解" << endl;
    }
    else
    {
        cout << "方程 " << a << "x + " << b << "y = " << c << endl;
        cout << "一组特解 x0=" << x0 << " , y0=" << y0 << endl;
        cout << "gcd(a,b) = " << g << endl;
        /*
            通解公式:
            x = x0 + (b/g)*t
            y = y0 - (a/g)*t
            t ∈ Z(全体整数)
        */
        long long dx = b / g;
        long long dy = -a / g;
        cout << "通解形式:x = " << x0 << " + " << dx << " * t ,  y = " << y0 << " + " << dy << " * t" << endl;
        cout << "遍历t = -2,-1,0,1,2 的几组解:" << endl;
        for(long long t = -2; t <= 2; t++)
        {
            long long x = x0 + dx * t;
            long long y = y0 + dy * t;
            cout << "t=" << t << "\tx=" << x << "\ty=" << y << endl;
        }
    }

    return 0;
}

运行输出

===== 置换运算测试 =====
置换f: 1 2 3 0 
置换g: 3 0 1 2 
复合 f∘g : 0 1 2 3 
f的逆置换 : 3 0 1 2 
f^3 : 3 0 1 2 
f^(-2): 2 3 0 1 

===== 不定方程求解 + 通解遍历 =====
方程 2x + 4y = 8
一组特解 x0=4 , y0=0
gcd(a,b) = 2
通解形式:x = 4 + 2 * t ,  y = 0 + -1 * t
遍历t = -2,-1,0,1,2 的几组解:
t=-2	x=0	y=2
t=-1	x=2	y=1
t=0	x=4	y=0
t=1	x=6	y=-1
t=2	x=8	y=-2

五、置换的阶与循环分解(竞赛解置换方程必备)

适用:C++ 竞赛(信息素养大赛算法应用竞赛初中组 / CSP-J 提高方向)
先记三句话:置换就是"谁去了哪";循环分解把置换拆成一堆不相交的圈;阶 = 所有圈长的最小公倍数(LCM)。解置换方程全靠循环分解 + 同余 + CRT。


1. 白话定义

置换 σ:一个排列 p[1..n],表示"元素 i 跑到位置 p[i]"。

σ: 1→2, 2→3, 3→1, 4→5, 5→4, 6→6
写成 p 数组:  p[1]=2  p[2]=3  p[3]=1  p[4]=5  p[5]=4  p[6]=6

置换的阶 ord(σ):最小的正整数 k,使得 σ^k = 恒等置换 e(谁都不动)。

循环分解:沿着 "1→2→3→1" 一直走,走回自己就是一个圈。上面这个置换拆成:

(1 2 3)(4 5)(6)     ← 三个互不相交的圈,圈长分别是 3、2、1

阶为什么等于 lcm? 因为 σ^k 在每个圈上 = "圈内往前挪 k 步"。想让每个圈都回到原位,k 必须同时是 3 的倍数、2 的倍数、1 的倍数 → k = lcm(3,2,1) = 6。

σ^1: (1→2→3→1)(4→5→4)(6→6)
σ^2: (1→3→2→1)(4→4)(5→5)(6→6)
...
σ^6 = e   →  ord(σ) = 6

2. 循环分解:算法 + 代码(逐行)

算法思路:开一个 vis 数组;从没访问过的点出发,顺着 p 一直走,直到踩回自己,途中经过的点就是一个圈;重复直到所有点都被分完。每个点最多被访问一次,复杂度 O(n)。

#include <bits/stdc++.h>
using namespace std;

long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; }
long long lcm(long long a, long long b) { return a / gcd(a, b) * b; } // 先除后乘防溢出

int main() {
    int n;
    cin >> n;
    vector<int> p(n + 1);
    for (int i = 1; i <= n; i++) cin >> p[i];   // 下标从 1 开始,数组开 n+1

    vector<bool> vis(n + 1, false);
    long long ord = 1;                          // 阶 = 所有圈长的 lcm
    for (int i = 1; i <= n; i++) {
        if (vis[i]) continue;                   // 已属于某个圈,跳过
        int cur = i, len = 0;
        while (!vis[cur]) {                     // 顺着置换走,走回已访问点说明圈走完了
            vis[cur] = true;
            cur = p[cur];
            len++;
        }
        ord = lcm(ord, len);                    // 每找到一个圈,就把它的长度并入 lcm
    }
    cout << ord << endl;                        // 输出置换的阶
    return 0;
}

解方程版本(需要保留每个圈内元素的先后顺序,后面解 σ^k = τ 要用):

struct Cycle { vector<int> a; };                // a[0..L-1]:圈内元素按 σ 走的顺序

vector<Cycle> decompose(int n, const vector<int>& p) {
    vector<bool> vis(n + 1, false);
    vector<Cycle> cyc;
    for (int i = 1; i <= n; i++) {
        if (vis[i]) continue;
        Cycle c;
        int cur = i;
        while (!vis[cur]) {                     // 同样地顺着 p 走
            vis[cur] = true;
            c.a.push_back(cur);
            cur = p[cur];
        }
        cyc.push_back(c);
    }
    return cyc;
}

3. 解置换方程 σ^k = τ(核心套路)

竞赛里最常见的方程是:给 σ 和 τ,求所有满足 σ^k = τ 的整数 k。

套路分四步,缺一不可:

第 1 步:先对 σ 循环分解

σ 的每个圈,长度记 L。σ^k 在这个圈上的效果 = 每个元素往前平移 k 步:

σ 的圈:  (a0 → a1 → a2 → ... → a_{L-1} → a0)
σ^k:      a_j → a_{(j+k) mod L}

平移不会改变圈结构,所以 τ 的圈结构必须和 σ 完全一致(圈数、每个圈长度都一样),否则直接无解。

第 2 步:每个圈压成一个同余约束

对 σ 的每个圈 (a0 a1 ... a_{L-1}):

  1. 在圈里找 τ(a0) 的位置 d,即 τ(a0) = a_d;
  2. 检查是不是每个 j 都满足 τ(a_j) = a_{(j+d) mod L}。只要有一个不满足,τ 就不是"纯平移",无解;
  3. 全部满足 → 得到一条约束:k ≡ d (mod L)。

第 3 步:CRT 合并所有约束

把每条 k ≡ d_i (mod L_i) 逐条合并(中国剩余定理,模数可能不互质,要用广义 CRT):

long long exgcd(long long a, long long b, long long &x, long long &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    long long g = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return g;
}

// 合并两条: k ≡ r1 (mod m1), k ≡ r2 (mod m2)
// 成功则更新 r1,m1 为合并结果;失败返回 false(无解)
bool mergeCRT(long long &r1, long long &m1, long long r2, long long m2) {
    long long x, y;
    long long g = exgcd(m1, m2, x, y);
    if ((r2 - r1) % g != 0) return false;            // 余数差必须能被 gcd 整除,否则无解
    long long mod2 = m2 / g;
    long long t = (((r2 - r1) / g) % mod2 + mod2) % mod2; // t ≡ (r2-r1)/g * inv(m1/g) (mod m2/g)
    t = t * ((x % mod2 + mod2) % mod2) % mod2;       // x 是 exgcd 里 m1/g 的逆元(模 m2/g 下)
    long long lcm = m1 / g * m2;                     // 新模数 = lcm(m1, m2)
    r1 = (r1 + m1 * t) % lcm;
    if (r1 < 0) r1 += lcm;
    m1 = lcm;
    return true;
}

第 4 步:写出通解

合并完得到 k ≡ k0 (mod M)(M = 所有圈长的 lcm,也就是 σ 的阶)。所有解是:

k = k0 + t·M ,   t ∈ ℤ(t 取任意整数)

这就是竞赛里"输出所有解"的标准答案形态——参数化通解。

对应到你之前那个例子里:解方程组得到 x = 4 + 2t, y = -t,形式一模一样——同余/不定方程联立后,解一定是 自由变量 t 的一次式,t 遍历整数就是全部解。置换方程里,合并 CRT 得到的正是这种形态,M 就是阶。


4. 配套工具:逆置换 & 置换快速幂

逆置换(σ^{-1}):"把 p 反过来",q[p[i]] = i。解 σ ∘ τ = e、或者方程两边同乘逆元时用:

vector<int> inverse(int n, const vector<int>& p) {
    vector<int> q(n + 1);
    for (int i = 1; i <= n; i++) q[p[i]] = i;   // p[i]=j  ⇔  q[j]=i
    return q;
}

置换快速幂(σ^k,k 可以很大):把"乘法"换成置换合成(O(n) 一次),套二进制幂:

// 合成约定:(a∘b)[x] = a[b[x]],即先按 b 走、再按 a 走
vector<int> compose(int n, const vector<int>& a, const vector<int>& b) {
    vector<int> c(n + 1);
    for (int x = 1; x <= n; x++) c[x] = a[b[x]];
    return c;
}

vector<int> permPow(int n, const vector<int>& p, long long k) {
    vector<int> res(n + 1), base = p;
    for (int i = 1; i <= n; i++) res[i] = i;    // res = 恒等置换 e
    while (k) {
        if (k & 1) res = compose(n, res, base); // 二进制位为 1 就乘上当前 base
        base = compose(n, base, base);          // base 自乘
        k >>= 1;
    }
    return res;
}

更优的每圈平移法(k 巨大时比快速幂还快,O(n)):预处理每圈,答案中位置 x 在圈内是第 idx 步,则 σ^k(x) = 圈内第 (idx + k) mod L 个元素。


5. 例题梯度(由易到难)

难度 题目 考点
入门 洛谷 P2921 [USACO08DEC] Trick or Treat on the Farm(万圣节奶牛) 函数图/置换上的循环分解 + 记忆化。每个点出发沿后继走,求第一次踩到已访问点要几步;把"进圈前的链"和"圈"分开算
基础 自测模板:读入 n 和置换 p,输出 ord(σ) 直接套第 2 节代码,练 lcm 合并
进阶 POJ 1026 Cipher(各大 OJ 可搜同名题) 对字符串做 k 次置换,k 可到 1e9。用每圈平移法 O(n) 预处理,别暴力 O(nk)
挑战 自编/校赛:解 σ^k = τ,输出所有 k 第 3 节完整套路:循环分解 → 每圈同余约束 → CRT → 通解 k = k0 + t·M

6. 易错点清单

  1. 下标从 1 开始,vector<int> p(n+1),别开成 n。
  2. 阶是 lcm 不是 max:圈长 4 和 6,阶是 lcm(4,6)=12,不是 6。
  3. lcm 先除后乘:a / gcd(a,b) * b,防止中间溢出;n 大、圈长乘积爆 long long 时要用 __int128 或判断题目范围。
  4. 解方程先比圈结构:τ 的圈数和每个圈长度必须与 σ 完全一致,不一致直接无解,别硬算。
  5. 每圈约束是"平移量":k ≡ d (mod L),d 是 τ 相对 σ 圈的偏移,不是圈长本身。
  6. CRT 模数可能不互质:必须用广义 CRT(先判余数差 % gcd),不能用互质版公式。
  7. 通解别丢 t:答案写成 k = k0 + t·M, t∈Z,竞赛里经常要求"最小正整数解"→ 取 k0 归一化到 (0, M] 区间。
  8. 快速幂的"乘法"是合成:compose 是 O(n),整体 O(n log k),别当成整数乘。

7. 一图流记忆

置换 σ ──循环分解──> 若干个圈 (长度 L1, L2, ..., Lm)
                        │
                        ├── 阶 ord(σ) = lcm(L1,...,Lm)     ← 求"最少重复几次回到原样"
                        │
                        └── σ^k 在每个圈上 = 平移 k 步
                              │
                              └── 解 σ^k = τ:每圈得 k ≡ d (mod L)
                                    └── CRT 合并 → k = k0 + t·M  ← 通解

1

  • 阶的求法:循环分解后,阶 = 所有圈长的 lcm(不是 max)。代码 O (n),lcm 要 "先除后乘" 防溢出。
  • 解 σ^k = τ 四步:① 对 σ 循环分解 → ② 每个圈检查 τ 是否纯平移,是则得约束 k ≡ d (mod L) → ③ 广义 CRT 合并(模数可能不互质,先判余数差 % gcd)→ ④ 通解 k = k0 + t·M,M 就是 σ 的阶。这和你之前那个 x = 4 + 2t, y = -t 是同一种参数化通解形态,t 遍历整数即全部解。

提交版:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// 扩展欧几里得:求 x,y 使 ax + by = gcd(a,b)
ll exgcd(ll a, ll b, ll& x, ll& y) {
    if (!b) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return g;
}

int main() {
    ll a, b, c;
    cin >> a >> b >> c;            // 读入方程 ax + by = c(提交版从标准输入读;原教学版可改为常量)
    ll x, y;
    ll g = exgcd(a, b, x, y);      // 先解 ax + by = g
    if (c % g) {                   // c 不能被 gcd 整除 → 无整数解
        cout << "No solution\n";
        return 0;
    }
    x *= c / g; y *= c / g;        // 放大到 ax + by = c,得到一组特解 (x, y)
    ll dx = b / g, dy = a / g;     // 通解:x = x0 + dx*t,y = y0 - dy*t,t 任意整数
    cout << g << '\n';
    cout << x << ' ' << y << '\n';
    cout << "x = " << x << " + " << dx << "*t, y = " << y << " - " << dy << "*t\n";
    for (int t = -2; t <= 2; t++)  // 遍历 t = -2..2 验证
        cout << "t=" << t << " x=" << x + dx * t << " y=" << y - dy * t << '\n';
}
posted @ 2026-10-12 11:12  gdyyx  阅读(3)  评论(0)    收藏  举报