一个置换问题

参考

二面体群的表示.csdn

问题

在玩扑克牌时定义出一种扑克置换 fn,v f_{n,v} fn,v (共n张牌,v张置底取顶这种操作形成的置换) 在无意的尝试中发现了 f5,2 = f5,1−1 f_{5,2}=f^{-1}{5,1} f5,2=f5,1−1的奇特现象 计算机验证了n在3000以内, 找不到 fn,k = fn,t−1 (k≠t) f{n,k}=f_{n,t}^{-1} (k \ne t) fn,k=fn,t−1(k=t) 的其他解, 因此有极大概率 n,k,t= 5,2,1 以外的特解不存在。

扑克置换定理

ff f 表示对单张牌状态的变换, f~ \tilde f f~表示对整个牌状态的变换

ff f 是一种操作,牌的初始状态为 1,2,3,...,n1, 2, 3, \dots, n 1,2,3,...,n,经过 ff f 作用后,牌的顺序变成了 f~(1,2,...,n) \tilde f(1, 2, \dots, n) f~(1,2,...,n)= f(1),f(2),f(3),...,f(n)f(1), f(2), f(3), \dots, f(n) f(1),f(2),f(3),...,f(n)

这个操作实际上定义出了一个函数,该函数是 {1,2,3,...,n}\{1, 2, 3, \dots, n\} {1,2,3,...,n} 上的一个置换。
( 1 2 ⋯ n f(1) f(2) ⋯ f(n) ) :=f~(1,2,...,n)=f(1),f(2),...,f(n) \begin{pmatrix} 1 & 2 & \cdots & n \\ f(1) & f(2) & \cdots & f(n) \end{pmatrix} := \tilde f(1, 2, \dots, n) = f(1), f(2), \dots, f(n) (1f(1)2f(2)⋯⋯nf(n)):=f~(1,2,...,n)=f(1),f(2),...,f(n)

目标 :求一个初始状态 ss s,使得 f~(s)=1,2,...,n \tilde f(s) = 1, 2, \dots, n f~(s)=1,2,...,n。

很容易猜出 s=f−1(1),f−1(2),...,f−1(n)s = f^{-1}(1), f^{-1}(2), \dots, f^{-1}(n) s=f−1(1),f−1(2),...,f−1(n),这里 f−1f^{-1} f−1 是指 ff f 的反函数。所以扑克置换问题最终归结为求函数 f−1f^{-1} f−1。


证明

  • P1 : 将 s=f−1(1),f−1(2),...,f−1(n)s = f^{-1}(1), f^{-1}(2), \dots, f^{-1}(n) s=f−1(1),f−1(2),...,f−1(n) 代入 f~(x) \tilde f(x) f~(x)
  • P2 :
    f~(s) =f~(f−1(1),f−1(2),...,f−1(n)) =f(f−1(1)),f(f−1(2)),...,f(f−1(n)) =1,2,3,...,n \begin{aligned} \tilde f(s) &= \tilde f(f^{-1}(1), f^{-1}(2), \dots, f^{-1}(n)) \\ &= f(f^{-1}(1)), f(f^{-1}(2)), \dots, f(f^{-1}(n)) \\ &= 1, 2, 3, \dots, n \end{aligned} f~(s)=f~(f−1(1),f−1(2),...,f−1(n))=f(f−1(1)),f(f−1(2)),...,f(f−1(n))=1,2,3,...,n

:具体的操作 ff f 可能很复杂,因此函数 ff f 通常用列表法表示。


f−1f^{-1} f−1 的求法

具体求 f−1f^{-1} f−1,总结了如下三种方法:

方法 1: 列表法

1,2,...,n1, 2, \dots, n 1,2,...,n 施加 ff f 得到状态 f(1),f(2),...,f(n)f(1), f(2), \dots, f(n) f(1),f(2),...,f(n),根据 ff f 再列出 f−1(1),f−1(2),...,f−1(n)f^{-1}(1), f^{-1}(2), \dots, f^{-1}(n) f−1(1),f−1(2),...,f−1(n)。

例子

ff f 为 1 张置底取顶 ,使用观察法得到 f−1f^{-1} f−1:

xx x 1 2 3 4 5
f(x)f(x) f(x) 2 4 1 5 3
f−1(x)f^{-1}(x) f−1(x) 3 1 5 2 4

方法 2: 循环置换

这个方法无需动脑,但很费手。 ff f是个 n-循环置换 ff f阶为 nn n,所以 e=f0=fne = f^0 = f^n e=f0=fn。 两侧乘以 f−1f^{-1} f−1 得到 f−1=fn−1f^{-1} = f^{n-1} f−1=fn−1。

具体做法 :对 1,2,...,n1, 2, \dots, n 1,2,...,n 连续施加 n−1n-1 n−1 次 ff f,即可得到 f−1f^{-1} f−1。

例子

用循环置换将 f1,f2,f3,f4,f5f^1, f^2, f^3, f^4, f^5 f1,f2,f3,f4,f5 列出如下(其中 f4=f−1f^4 = f^{-1} f4=f−1):

xx x 1 2 3 4 5
f1(x)f^1(x) f1(x) 2 4 1 5 3
f2(x)f^2(x) f2(x) 4 5 2 3 1
f3(x)f^3(x) f3(x) 5 3 4 1 2
f4(x)f^4(x) f4(x) 3 1 5 2 4
f5(x)f^5(x) f5(x) 1 2 3 4 5

方法 3: 对偶原理

这个方法最野,也最快。 这不是严谨数学, 这里只是一个能解决问题的技巧 牌区分 手上,桌上,不是简单的一个牌序 这要靠想象力,将电影倒放 这里分析了词项,猜测尝试得到了有效的对偶操作 两类对偶的结果 对 词项的解释不同

对偶原理.csdn 博大精深,但找到恰当的对偶对是有点困难的。

FF F 是一个操作, FF F 的对偶是 F−1F^{-1} F−1。 F−1=逆操作FF^{-1} = \text{逆操作}F F−1=逆操作F。

F是一个操作单元,重复施加5次F 形成了最终的置换f

操作约定

桌底的牌比手上的牌更靠下方 手上的牌经过一次F操作,会转移到桌子最上方

  • 置底: 把手上最顶的牌放到手上最底
  • 取顶: 把手上最顶的牌放到桌子上
  • 置顶: 把手上最底的牌放到手上最顶
  • 取底: 把手上最底的牌放到桌子上
  • 个体词:底,顶
  • 谓词:置,取

逆变换操作

这是不严格的,是复合运算求逆的尝试和拼凑

复合的逆公式为: (A∘B)−1=B−1∘A−1(A \circ B)^{-1} = B^{-1} \circ A^{-1} (A∘B)−1=B−1∘A−1

(置底∘取顶)−1= 取顶−1 ∘ 置底−1 =置顶∘取底 (置_底∘取_顶)^{-1} =取_顶^{-1} ∘置_底^{-1} =置_顶∘取_底 (置底∘取顶)−1=取顶−1∘置底−1=置顶∘取底

FF F 置底∘取顶置底∘取顶 置底∘取顶 F−1F^{-1} F−1 置顶∘取底置顶∘取底 置顶∘取底

Fi F_i Fi :指 FF F操作了 ii i次 Fi−1 F_i^{-1} Fi−1 : 指 F−1F^{-1} F−1操作了 ii i次

初始状态手上牌的状态

生成状态S的过程

初始状态 F0−1 初始状态F_0^{-1} 初始状态F0−1 F1−1 F_1^{-1} F1−1 F2−1 F_2^{-1} F2−1 F3−1 F_3^{-1} F3−1 F4−1 F_4^{-1} F4−1 F5−1 F_5^{-1} F5−1
手1 手5 手3 手1 手 3 卓3
手2 手1 手5 手3 卓1 卓1
手3 手2 手1 卓5 卓5 卓5
手4 手3 卓2 卓2 卓2 卓2
手5 卓4 卓4 卓4 卓4 卓4

验证状态S的过程 先放桌子上的牌是先亮出来的

初始状态F0 初始状态F_0 初始状态F0 F1 F_1 F1 F2 F_2 F2 F3 F_3 F3 F4 F_4 F4 F5 F_5 F5
手3 手5 手4 手5 手 5 卓5
手1 手2 手3 手4 卓4 卓4
手5 手4 手5 卓3 卓3 卓3
手2 手3 卓2 卓2 卓2 卓2
手4 卓1 卓1 卓1 卓1 卓1

逆时间下的对偶操作

这是严格的,有逻辑的,就是时间反演下的操作的直接翻译
手上牌 和 桌上牌 都是牌面朝下 左侧的 置底 置_底 置底是手上的顶置手上的底 左侧的 取顶 取_顶 取顶是取手上的顶到桌上的底 右侧的 取底 取_底 取底是取桌上牌的底到手上的顶 右侧的 置顶 置_顶 置顶是 手上牌的底到手上的顶

(置底∘取顶)′=取顶′∘置底′=取底∘置顶 (置_底∘取_顶)' =取_顶' ∘置_底' =取_底∘置_顶 (置底∘取顶)′=取顶′∘置底′=取底∘置顶

操作的精细化表达

这样表达才是清晰精确的

AA A :手 BB B: 卓 A0 A_0 A0 : 手下 A1 A_1 A1 : 手上 B0 B_0 B0 : 卓下 B1 B_1 B1 : 卓上 A1A0 A_1A_0 A1A0 : 手上 转移 到 手下 A0B0 A_0B_0 A0B0 : 手下 转移 到 卓下 A1B1 A_1B_1 A1B1 : 手上 转移 到 卓上 (A1A0)′=A0A1 (A_1A_0)'=A_0A_1 (A1A0)′=A0A1: 手下 转移 到 手上

(置底∘取顶)′=取顶′∘置底′=取底∘置顶 (置_底∘取_顶)' =取_顶' ∘置_底' =取_底∘置_顶 (置底∘取顶)′=取顶′∘置底′=取底∘置顶 左侧= A1A0 A_1A_0 A1A0∘ A1B0 A_1B_0 A1B0 (A1A0 (A_1A_0 (A1A0∘ A1B0)′=B0A1∘A0A1 A_1B_0)'=B_0A_1∘A_0A_1 A1B0)′=B0A1∘A0A1

f的另一种神奇的逆g

gg g 为 2 张置底取顶

2张置底取顶操作g 和 1张置底取顶操作f 互为逆操作

xx x 1 2 3 4 5
g(x)g(x) g(x) 3 1 5 2 4
g−1(x)g^{-1}(x) g−1(x) 2 4 1 5 3

️ 注意

在有 5 张牌时,1 张置底取顶2 张置底取顶 互为反函数,但这不具有一般性。当牌多的时候,两者并无明显的关系。

g=f−1g=f^{-1} g=f−1的一般化和构造问题

这个和约瑟夫问题很像,但并不一样, 我还没有找到解决办法

约定

在操作过程中,手上牌面朝下, 桌上牌面朝上 最终的牌序是 将牌朝下, 顶上的牌为第一张

F(n,k,t):= fn,k = fn,t−1 F(n,k,t) := f_{n,k}=f_{n,t}^{-1} F(n,k,t):=fn,k=fn,t−1 σv:= fn,v (n固定) σ_v := f_{n,v} (n固定) σv:=fn,v(n固定)

因为 fn,v 因为f_{n,v} 因为fn,v 的解析式很难构造出,让人感觉无从下手

扑克置换 fn,v 扑克置换f_{n,v} 扑克置换fn,v : 共n张牌,手上顶部的v张牌,置手底.再取手顶上的牌,倒扣到桌上的操作(当手上牌数 ≤v\le v ≤v,则手上的v张牌全倒扣在桌子上) 是否存在 n,k,t使得 fn,k = fn,t−1 f_{n,k}=f_{n,t}^{-1} fn,k=fn,t−1 首先已经验证 n,k,t=5,2,1n,k,t=5,2,1 n,k,t=5,2,1 是成立的, 问题是 当 n,k,t满足什么关系时 fn,k = fn,t−1 f_{n,k}=f_{n,t}^{-1} fn,k=fn,t−1 才成立

或者说 Fn={  fn,v ∣0≤v<n } \mathcal{F}n=\{\,f{n,v}\mid 0\le v<n\,\} Fn={fn,v∣0≤v<n}
F= ⋃n≥1 Fn={  fn,v ∣n≥1, 0≤v<n }. \mathcal{F} =\bigcup_{n\ge1}\mathcal{F}n =\{\,f{n,v}\mid n\ge1,\ 0\le v<n\,\}. F=n≥1⋃Fn={fn,v∣n≥1, 0≤v<n}.

n,k,t=5,2,1n,k,t=5,2,1 n,k,t=5,2,1 是 fn,k = fn,t−1 f_{n,k}=f_{n,t}^{-1} fn,k=fn,t−1 成立的一个组合,是否有高效的算法找到更多的其他组合?

分析 F(n,k,t)F(n,k,t) F(n,k,t)

  • 首先 fn,v f_{n,v} fn,v和 fn,v−1 f^{-1}{n,v} fn,v−1的阶一样,即 ord( fn,k )=ord( fn,t ) ord(f{n,k}) =ord(f_{n,t}) ord(fn,k)=ord(fn,t)这能减少一些计算量
  • fn,v = fn,v−1 f_{n,v}=f^{-1}_{n,v} fn,v=fn,v−1 是满足自逆的大类
  • 可以通过对 1,2...n1,2...n 1,2...n连续施加两次能回到 1,2...n1,2...n 1,2...n验证
  • Fn={  fn,v ∣0≤v<n } \mathcal{F}n=\{\,f{n,v}\mid 0\le v<n\,\} Fn={fn,v∣0≤v<n} 只是 Sn S_n Sn的一个很小子集,一般不构成群 如果 Fn \mathcal{F}_n Fn能够构成一个群,则有概率存在非平凡特解
  • fn,0 =σ0是Sn的单位元 f_{n,0}=σ_0是S_n的单位元 fn,0=σ0是Sn的单位元
  • fn,v (1)=σv(1)=v+1 f_{n,v}(1)=σ_v(1)=v+1 fn,v(1)=σv(1)=v+1
  • 其中 fn,0 = fn,0−1 f_{n,0}=f_{n,0}^{-1} fn,0=fn,0−1 是自逆类的平凡解
  • 其中 fn,k = fn,t−1 f_{n,k}=f_{n,t}^{-1} fn,k=fn,t−1 ( k≠tk \ne t k=t)是非平凡解
  • 非平凡解是非常罕见的, 甚至有很大概率只有 5,2,1 这一组
n k t
m 0 0
3 1 1
5 2 1

Fn \mathcal{F}_n Fn 不是 Sn S_n Sn的子群 ( n≥3n \ge3 n≥3)

因为 Fn(n≥3) \mathcal{F}_n(n \ge3) Fn(n≥3)无法构成群,所以 F(n,k,t)F(n,k,t) F(n,k,t)能找到非平凡解的概率很低

假设 Fn \mathcal{F}_n Fn 是 Sn S_n Sn的1个n阶子群

  • τ= fn,1 ∘ fn,2 ∈Fn τ=f_{n,1} ∘f_{n,2} \in \mathcal{F}_n τ=fn,1∘fn,2∈Fn
  • fn,0 f_{n,0} fn,0是 Sn S_n Sn的单位元 ee e
  • fn,v (1)=σv(1)=v+1 f_{n,v}(1)=σ_v(1)=v+1 fn,v(1)=σv(1)=v+1
  • τ(1)= fn,1 ( fn,2 (1))= fn,1 (3)=3τ(1)=f_{n,1}(f_{n,2}(1))=f_{n,1}(3)=3 τ(1)=fn,1(fn,2(1))=fn,1(3)=3
  • 如果 τ∈Fn τ \in \mathcal{F}n τ∈Fn,则必有 τ= fn,2 τ=f{n,2} τ=fn,2
  • τ= fn,1 ∘ fn,2 = fn,2 τ=f_{n,1}∘f_{n,2}=f_{n,2} τ=fn,1∘fn,2=fn,2 所以 fn,1 =e f_{n,1}=e fn,1=e (矛盾)
  • 假设不成立

编程实现 f(n,t,s)

f(5,2,f(5,1)) =1, 2, 3, 4, 5

js 复制代码
/**
 * 模拟 f 操作:将手牌每次将顶部 t 张移到底部,然后取 1 张放到桌上。
 * @param {number} n - 牌的总数(用于生成默认牌序)
 * @param {number} t - 每次置底的牌数
 * @param {number[]} [s] - 初始手牌数组。如果不填,默认生成 [1, 2, ..., n]
 * @returns {number[]} - 返回出牌顺序的数组
 */
function f(n, t, s = Array.from({ length: n }, (_, i) => i + 1)) {
    // 防御性处理:如果传入的初始牌序为空,直接返回空数组
    if (!s || s.length === 0) return [];
    
    // 特殊情况:如果不置底,直接依次取走
    if (t === 0) return [...s];

    // 复制一份手牌,避免修改外部传入的原数组
    let hand = [...s];
    const result = []; // 记录桌上的牌(出牌顺序)

    while (hand.length > 0) {
        // 规则:当手上牌数 <= t,则手上的牌全倒扣在桌子上
        if (hand.length <= t) {
            result.push(...hand);
            break;
        }

        // 1. 置底操作:将顶部 t 张牌移到底部
        const topCards = hand.splice(0, t);
        hand.push(...topCards);

        // 2. 取顶操作:将新的顶部 1 张牌放到桌上
        result.push(hand.shift());
    }

    return result;
}

// --- 测试验证 ---
f(5,2,f(5,1))

编程寻找 F(n,k,t)F(n,k,t) F(n,k,t)的非平凡解

随着n的增大,计算量暴涨 已经验证了在 n≤3000n \le 3000 n≤3000 范围内 只有 5,2,1 这一组非平凡解 所以大概率不存在其他非平凡解了

bash 复制代码
用法: ./pkzh -start_n=<起始n> -max_n=<终止n> 
# 已经验证了
./pkzh -start_n=1  -max_n=3000
main.cpp
c 复制代码
#include <iostream>
#include <vector>
#include <numeric>  // iota 函数头文件
#include <thread>   // 多线程支持
#include <mutex>    // 互斥锁
#include <fstream>  // 文件操作
#include <sstream>  // 字符串流
#include <string_view> // C++17 字符串视图
#include <cstdint>  // 固定宽度整数类型
#include <algorithm> // std::gcd 和其他算法

using namespace std;

// 全局互斥锁,用于保护多线程环境下的输出
mutex io_mutex;

// 缓存每个 v 值对应的排列数据
struct SigmaCache {
    vector<uint16_t> perm;     // σ_v 的排列结果 (长度为 n)
    vector<uint16_t> inv_perm; // σ_v 的逆排列 (满足 inv_perm[perm[i]-1] = i+1)
    int ord;                   // 该排列的阶(最小正整数 k 使得 σ^k = identity)
};

/**
 * 模拟"跳过 v 张牌取一张"的发牌过程
 *
 * 规则说明:
 * 1. 初始牌堆为 [1, 2, 3, ..., n]
 * 2. 从第 1 张牌开始,每次跳过 v 张牌(循环计数),取下一张牌
 * 3. 重复直到牌堆为空
 *
 * @param n      牌堆总张数
 * @param v      每次跳过的牌数
 * @param init   初始牌堆(应为 [1,2,...,n])
 * @param out    输出排列(结果将存储在此向量)
 */
void simulate_fast(int n, int v, const vector<uint16_t>& init, vector<uint16_t>& out)
{
    out.clear();
    vector<uint16_t> hand = init; // 复制初始牌堆(避免修改原数据)
    int ptr = 0;                  // 当前指针位置(指向待跳过的起始位置)

    while (!hand.empty()) {
        // 情况1:剩余牌数 ≤ v,直接输出所有剩余牌(无需跳过)
        if (hand.size() <= v) {
            out.insert(out.end(), hand.begin(), hand.end());
            break;
        }

        // 情况2:跳过 v 张牌(循环处理,自动处理越界)
        ptr = (ptr + v) % hand.size();

        // 取当前指针指向的牌并移除
        out.push_back(hand[ptr]);
        hand.erase(hand.begin() + ptr);

        // 注意:erase 后 ptr 自动指向被删除元素的下一个位置
        // 例如:[A,B,C] 删除 ptr=1(B) 后,ptr 自动指向 C(索引1)
    }
    // 保证输出长度恒为 n(关键修复点)
}

/**
 * 计算排列的阶(order)
 *
 * 排列阶的定义:最小正整数 k 使得 σ^k = identity
 * 计算方法:分解为不相交轮换,阶 = 各轮换长度的最小公倍数
 *
 * @param p  排列数组(0-indexed,p[i] 表示 i+1 映射到的值)
 * @param n  排列长度
 * @return   排列的阶
 */
int permutation_order(const vector<uint16_t>& p, int n)
{
    if (n <= 1) return 1;

    vector<bool> vis(n + 1, false); // 访问标记数组(1-indexed)
    int order = 1;                  // 当前计算的阶

    for (int i = 1; i <= n; ++i) {
        if (vis[i]) continue; // 跳过已访问元素

        // 追踪当前轮换
        int cur = i;
        int len = 0;          // 轮换长度
        while (!vis[cur]) {
            vis[cur] = true;
            cur = p[cur - 1]; // 注意:p 是 0-indexed,cur 是 1-indexed
            len++;
        }

        // 更新阶:LCM(当前阶, 轮换长度)
        int g = std::gcd(order, len);
        order = order / g * len;
    }
    return order;
}

/**
 * 工作线程函数:在指定区间 [start_k, end_k) 内搜索解
 *
 * 搜索条件:找到所有 (k,t) 满足 σ_t = σ_k^{-1} 且 ord(σ_k) = ord(σ_t)
 *
 * @param n           当前处理的 n 值
 * @param start_k     k 的搜索起始值(包含)
 * @param end_k       k 的搜索结束值(不包含)
 * @param cache       预计算的 SigmaCache 数组(索引 0~n-1)
 * @param local_results 线程本地结果存储
 */
void worker(int n, int start_k, int end_k,
            const vector<SigmaCache>& cache,
            vector<pair<int, int>>& local_results)
{
    local_results.clear();
    if (n <= 1) return;

    for (int k = start_k; k < end_k; ++k) {
        const auto& ck = cache[k];   // 获取 σ_k 的数据
        int ok = ck.ord;             // σ_k 的阶
        const auto& inv_k = ck.inv_perm; // σ_k^{-1}

        // 仅需检查 t > k(避免重复和自反情况)
        for (int t = k + 1; t < n; ++t) {
            // 剪枝1:阶必须相等
            if (cache[t].ord != ok) continue;

            // 剪枝2:检查 σ_t 是否等于 σ_k^{-1}
            if (cache[t].perm == inv_k) {
                local_results.emplace_back(k, t);
            }
        }
    }
}

/**
 * 并行搜索主函数
 *
 * 流程:
 * 1. 对每个 n ∈ [start_n, max_n]
 * 2. 生成所有 v ∈ [0, n-1] 对应的 σ_v
 * 3. 并行搜索满足 σ_t = σ_k^{-1} 的 (k,t) 对
 * 4. 将结果写入文件
 *
 * @param start_n        起始 n 值
 * @param max_n          最大 n 值
 * @param output_filename 输出文件名
 * @param custom_threads  指定线程数(0 表示自动检测)
 */
void find_solutions_parallel(int start_n, int max_n,
                             const string& output_filename,
                             unsigned int custom_threads)
{
    ofstream out_file(output_filename, ios::app); // 追加模式打开文件
    if (!out_file.is_open()) {
        cerr << "错误:无法打开文件 " << output_filename << endl;
        return;
    }

    // 自动检测硬件线程数
    unsigned int num_threads = custom_threads;
    if (num_threads == 0) {
        num_threads = thread::hardware_concurrency();
        if (num_threads == 0) num_threads = 4; // 检测失败时默认值
    }
    cout << "搜索区间 n = " << start_n << " ~ " << max_n << endl;
    cout << "并发线程数:" << num_threads << "\n------------------------\n";

    for (int n = start_n; n <= max_n; ++n) {
        // 处理 n=1 的特殊情况
        if (n == 1) {
            cout << ">>> n=1 遍历完成 \n";
            continue;
        }

        // 创建初始牌堆 [1, 2, ..., n]
        vector<uint16_t> identity(n);
        iota(identity.begin(), identity.end(), 1);

        // 预计算所有 v ∈ [0, n-1] 对应的排列数据
        vector<SigmaCache> cache(n);
        for (int v = 0; v < n; ++v) {
            auto& entry = cache[v];
            // 生成 σ_v 排列
            simulate_fast(n, v, identity, entry.perm);

            // 计算逆排列:inv_perm[x-1] = y+1 当且仅当 perm[y] = x
            entry.inv_perm.resize(n);
            for (int i = 0; i < n; ++i) {
                uint16_t val = entry.perm[i]; // val = σ_v(i+1)
                entry.inv_perm[val - 1] = i + 1; // σ_v^{-1}(val) = i+1
            }

            // 计算排列阶
            entry.ord = permutation_order(entry.perm, n);
        }

        // ========== 并行搜索阶段 ==========
        vector<thread> threads;
        vector<vector<pair<int, int>>> thread_results(num_threads); // 每个线程的结果存储
        int total_k = n; // k 的取值范围 [0, n-1]
        int base = total_k / (int)num_threads; // 基础任务量
        int rem = total_k % (int)num_threads; // 余数(分配给前 rem 个线程)
        int cur = 0; // 当前任务起始位置

        // 创建工作线程
        for (unsigned int i = 0; i < num_threads; ++i) {
            int chunk = base + (i < rem ? 1 : 0); // 任务块大小
            int sk = cur;      // 任务块起始 k
            int ek = cur + chunk; // 任务块结束 k
            cur = ek;

            if (sk < n) {
                // 启动线程处理 [sk, ek) 区间
                threads.emplace_back(worker, n, sk, ek, cref(cache), ref(thread_results[i]));
            }
        }

        // 等待所有线程完成
        for (auto& th : threads) th.join();

        // ========== 结果收集阶段 ==========
        bool found = false;
        {
            // 加锁保护共享输出资源
            lock_guard<mutex> lock(io_mutex);

            // 合并所有线程的结果
            for (auto& res : thread_results) {
                for (auto [k, t] : res) {
                    // 打印发现的解(包含对称解)
                    cout << "[找到解] n=" << n << " k=" << k << " t=" << t << "\n";
                    cout << "[对称解] n=" << n << " k=" << t << " t=" << k << "\n";

                    // 发现解后立刻写入文件
                    out_file << n << "," << k << "," << t << "\n";
                    out_file << n << "," << t << "," << k << "\n";

                    // 立刻刷新到磁盘,确保数据不丢失
                    out_file.flush();

                    found = true;
                }
            }
            // 输出当前 n 的状态
            cout << ">>> n=" << n << " 遍历完成 " << (found ? "【存在解】" : "") << "\n";
        }
    }

    out_file.close();
    cout << "\n全部区间搜索完成,结果已保存\n";
}

/**
 * 打印程序使用说明
 *
 * @param prog 程序名(argv[0])
 */
void print_usage(const char* prog)
{
    cout << "用法: " << prog << " -start_n=xx -max_n=xx [选项]\n"
         << "必选:\n"
         << "  -start_n=NUM    起始n\n"
         << "  -max_n=NUM      终止n\n"
         << "可选:\n"
         << "  -threads=NUM    线程数\n"
         << "  -output=FILE    输出文件\n"
         << "示例:\n"
         << "  ./pkzh -start_n=500 -max_n=1000 -threads=12 -output=500_1000.txt\n";
}

/**
 * 主函数:解析命令行参数并启动搜索
 */
int main(int argc, char* argv[])
{
    // 无参数时显示帮助
    if (argc == 1) {
        print_usage(argv[0]);
        return 0;
    }

    int start_n = -1, max_n = -1;
    unsigned int threads = 0;
    string out_name = "solutions_output.txt"; // 默认输出文件

    // 解析命令行参数
    for (int i = 1; i < argc; ++i) {
        string_view arg(argv[i]);
        size_t eq = arg.find('=');
        string_view key, val;

        // 处理 key=value 形式
        if (eq != string_view::npos) {
            key = arg.substr(0, eq);
            val = arg.substr(eq + 1);
        }
            // 处理 key value 形式(下一个参数作为值)
        else {
            key = arg;
            if (i + 1 >= argc) {
                cerr << "参数 " << arg << " 缺少值\n";
                print_usage(argv[0]);
                return 1;
            }
            val = argv[++i];
        }

        try {
            if (key == "-start_n") start_n = stoi(string(val));
            else if (key == "-max_n") max_n = stoi(string(val));
            else if (key == "-threads") threads = stoul(string(val));
            else if (key == "-output") out_name = string(val);
            else {
                cerr << "未知参数: " << key << "\n";
                print_usage(argv[0]);
                return 1;
            }
        } catch (...) {
            cerr << "参数值必须是数字\n";
            print_usage(argv[0]);
            return 1;
        }
    }

    // 验证必要参数
    if (start_n == -1 || max_n == -1) {
        cerr << "缺少 -start_n / -max_n\n";
        print_usage(argv[0]);
        return 1;
    }
    if (start_n > max_n) {
        cerr << "start_n 不能大于 max_n\n";
        return 1;
    }

    // 启动搜索
    find_solutions_parallel(start_n, max_n, out_name, threads);
    return 0;
}
CMakeLists.txt
bash 复制代码
cmake_minimum_required(VERSION 3.10)
project(pkzh)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(pkzh main.cpp)

# 开启最高优化并针对当前CPU架构
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -march=native")

# 线程依赖
find_package(Threads REQUIRED)
target_link_libraries(pkzh PRIVATE Threads::Threads)
相关推荐
默_笙1 小时前
🌀 别再手动写 Prompt 了!我让 AI 自己循环改到满意为止
javascript
To_OC13 小时前
LC 994 腐烂的橘子:人人都说是 BFS 入门题,我却写了三遍才过
javascript·算法·leetcode
To_OC19 小时前
LC 200 岛屿数量:经典 DFS 入门题,我第一次写居然连方向都搞错了
javascript·算法·leetcode
labixiong21 小时前
实现一个能跑的迷你版Promise(一)
前端·javascript·面试
weedsfly1 天前
还在用 Axios?你可能需要重新理解 XHR 与 Fetch
前端·javascript·面试
CoderWeen1 天前
从零实现一个 Vue3 流程图编辑器:节点拖拽、贝塞尔连线与框选
前端·javascript
To_OC1 天前
LC 128 最长连续序列:别上来就排序,O (n) 解法才是这题的灵魂
javascript·算法·leetcode
kyriewen2 天前
我用 50 行代码重写了 React Router 核心,终于搞懂了前端路由原理
前端·javascript·react.js
Asize2 天前
HTML5 Canvas 基础:从按帧动画到 ECharts 数据可视化
前端·javascript·canvas