置换群的运算与应用
这是置换群运算应用的算法编程的研究学习。
一、概念名称的明析
| 图片原文叫法 | 代码里使用名称(修改后) | 标准置换群叫法 |
|---|---|---|
| 加法对应关系: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] | 置换乘法(底层实现:置换快速幂) | 置换的整数倍幂运算 |
✅ 关键约定(后面完整代码全部改成这套命名)
- 函数名:
plus_perm→ 置换加法(题目定义的加法,本质置换复合) - 函数名:
minus_perm→ 置换减法(题目定义的减法,本质逆复合) - 函数名:
muti→ 置换乘法(题目定义的乘整数 k,用置换快速幂实现)
二、运算法则



小结:
| 名称 | 记号 | 含义 |
|---|---|---|
| 置换复合 | σ∘τ | 先 τ,再 σ |
| 置换逆复合 | σ−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;
}
函数对照说明
plus_perm:对应 Pascal 的plus,置换复合,计算p+qminus_perm:对应 Pascal 的minus,求p−q的置换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 时刻密码。
- 置换复合
plus_perm(p+q) - 置换减法
minus_perm(p−q) - 置换快速幂
muti(t 倍置换) - 扩展欧几里得算法,求解不定方程 (x\cdot t_1+y\cdot t_2 =k)
- 主逻辑:贝祖定理判断有无解,算出 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;
}
说明
- exgcd:算出 (t_1 x + t_2 y =k) 的整数解 x,y;
- 无解条件:
k % gcd(t1,t2) != 0,直接输出无解
- 无解条件:
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;
}
说明
- 拆分了
muti_pos(只处理≥0 倍数)和通用muti函数 - 当
s<0:- 用
minus_perm(恒等置换, a, inv_a, n)求出逆置换 - 再对逆置换做正数倍的快速幂
- 用
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 只是一组特解,有无穷多组整数解:

不同的 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
竞赛规则说明
- 无解输出
NO;有解输出YES,后面输出置换序列。 - 变量全部用
long long,防止 t1,t2,k 很大溢出。 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、完整合并代码:置换运算 + 扩展欧几里得 + 不定方程特解、通解遍历
术语约定:
compose:置换复合 (f\circ g),先 g 后 finverse_perm:置换逆复合,求 (p^{-1})perm_pow:置换整数幂 (p^k)(k 正负 0 都支持)- 数学问题模型:求整数 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}):
- 在圈里找 τ(a0) 的位置 d,即 τ(a0) = a_d;
- 检查是不是每个 j 都满足 τ(a_j) = a_{(j+d) mod L}。只要有一个不满足,τ 就不是"纯平移",无解;
- 全部满足 → 得到一条约束: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 开始,
vector<int> p(n+1),别开成 n。 - 阶是 lcm 不是 max:圈长 4 和 6,阶是 lcm(4,6)=12,不是 6。
- lcm 先除后乘:
a / gcd(a,b) * b,防止中间溢出;n 大、圈长乘积爆 long long 时要用__int128或判断题目范围。 - 解方程先比圈结构:τ 的圈数和每个圈长度必须与 σ 完全一致,不一致直接无解,别硬算。
- 每圈约束是"平移量":
k ≡ d (mod L),d 是 τ 相对 σ 圈的偏移,不是圈长本身。 - CRT 模数可能不互质:必须用广义 CRT(先判余数差 % gcd),不能用互质版公式。
- 通解别丢 t:答案写成
k = k0 + t·M, t∈Z,竞赛里经常要求"最小正整数解"→ 取k0归一化到 (0, M] 区间。 - 快速幂的"乘法"是合成:
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 ← 通解

- 阶的求法:循环分解后,
阶 = 所有圈长的 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';
}

浙公网安备 33010602011771号