蓝桥杯模拟赛--康托展开式--全排列(不重复数据)

本文介绍了康托展开和逆展开的基本概念,并通过实例详细解释了如何利用这些数学技巧进行全排列的计算。同时,提供了Java实现代码,帮助读者更好地理解和应用。

题目是这样的:
标题: 排列序数

X星系的某次考古活动发现了史前智能痕迹。
这是一些用来计数的符号,经过分析它的计数规律如下:
(为了表示方便,我们把这些奇怪的符号用a~q代替)
abcdefghijklmnopq 表示0
abcdefghijklmnoqp 表示1
abcdefghijklmnpoq 表示2
abcdefghijklmnpqo 表示3
abcdefghijklmnqop 表示4
abcdefghijklmnqpo 表示5
abcdefghijklmonpq 表示6
abcdefghijklmonqp 表示7
.....

问题一:
在一处石头上刻的符号是:
bckfqlajhemgiodnp
请你计算出它表示的数字是多少?

问题二:
求第22952601027516个排列的符号排列是?


先学习康托展开式:转自http://www.cnblogs.com/1-2-3/archive/2011/04/25/generate-permutation-part2.html

康托展开的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始)。
  这个公式可能看着让人头大,最好举个例子来说明一下。例如,有一个数组 s = ["A", "B", "C", "D"],它的一个排列 s1 = ["D", "B", "A", "C"],现在要把 s1 映射成 X。n 指的是数组的长度,也就是4,所以
X(s1) = a4*3! + a3*2! + a2*1! + a1*0!
关键问题是 a4、a3、a2 和 a1 等于啥?
a4 = "D" 这个元素在子数组 ["D", "B", "A", "C"] 中是第几大的元素。"A"是第0大的元素,"B"是第1大的元素,"C" 是第2大的元素,"D"是第3大的元素,所以 a4 = 3。
a3 = "B" 这个元素在子数组 ["B", "A", "C"] 中是第几大的元素。"A"是第0大的元素,"B"是第1大的元素,"C" 是第2大的元素,所以 a3 = 1。
a2 = "A" 这个元素在子数组 ["A", "C"] 中是第几大的元素。"A"是第0大的元素,"C"是第1大的元素,所以 a2 = 0。
a1 = "C" 这个元素在子数组 ["C"] 中是第几大的元素。"C" 是第0大的元素,所以 a1 = 0。(因为子数组只有1个元素,所以a1总是为
所以,X(s1) = 3*3! + 1*2! + 0*1! + 0*0! = 20



通过康托逆展开生成全排列

  如果已知 s = ["A", "B", "C", "D"],X(s1) = 20,能否推出 s1 = ["D", "B", "A", "C"] 呢?
  因为已知 X(s1) = a4*3! + a3*2! + a2*1! + a1*0! = 20,所以问题变成由 20 能否唯一地映射出一组 a4、a3、a2、a1?如果不考虑 ai 的取值范围,有
3*3! + 1*2! + 0*1! + 0*0! = 20
2*3! + 4*2! + 0*1! + 0*0! = 20
1*3! + 7*2! + 0*1! + 0*0! = 20
0*3! + 10*2! + 0*1! + 0*0! = 20
0*3! + 0*2! + 20*1! + 0*0! = 20
等等。但是满足 0 <= ai <= n-1 的只有第一组。可以使用辗转相除的方法得到 ai,如下图所示:

知道了a4、a3、a2、a1的值,就可以知道s1[0] 是子数组["A", "B", "C", "D"]中第3大的元素 "D",s1[1] 是子数组 ["A", "B", "C"] 中第1大的元素"B",s1[2] 是子数组 ["A", "C"] 中第0大的元素"A",s[3] 是子数组 ["C"] 中第0大的元素"C",所以s1 = ["D", "B", "A", "C"]。

import java.util.ArrayList;
import java.util.Iterator;

public class Main6 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		/* abcdefghijklmnopq 表示0
		   abcdefghijklmnoqp 表示1
		   abcdefghijklmnpoq 表示2
		   abcdefghijklmnpqo 表示3
		 * 求bckfqlajhemgiodnp代表的数字
		 * */
		long a[] = new long[17];
		GetFactorial(a);
		long sum = Getansnum("bckfqlajhemgiodnp",a);
		System.out.println(sum);
		
		/* abcdefghijklmnopq 表示0
		   abcdefghijklmnoqp 表示1
		   abcdefghijklmnpoq 表示2
		   abcdefghijklmnpqo 表示3
		   求第22952601027516个排列的符号排列是?
		 * */      
		long num = 22952601027516L;
		String ans = Getanssign(a,num);
		System.out.println(ans);
	}

	
	private static String Getanssign(long[] a, long num) {
		ArrayList<Character> sub = new ArrayList<>();
		ArrayList<Character> result = new ArrayList<>();
		for (int i = 0; i <=16; i++) {
			sub.add((char) ('a'+i));//生产a-q升序排列;
		}
		for(int i = 16;i>=0;i--){
			int ai;
			if (i==0) {
				ai = 0;
			}else {
				ai = (int) (num/a[i]);
			}
			char temp = sub.get(ai);
			result.add(temp);
			sub.remove(sub.indexOf(temp));
			if (i==0) {
				num = 0;
			}else {
				num = num%a[i];	
			}
		}
		Iterator<Character> ansstring = result.iterator();
		StringBuffer answer = new StringBuffer();
		while(ansstring.hasNext()){
			answer.append(ansstring.next());
		}
		return answer.toString();
	}


	/**
	 * @param s 已知的符号序列
	 * @param a 阶乘数组
	 * @return s符号序列的是第几个排列
	 */
	private static long Getansnum(String s, long[] a) {
		// TODO Auto-generated method stub
		long sum = 0;
    	char temp[] = s.toCharArray();
    	int num[] = new int[s.length()];
    	for (int i = 0; i < temp.length; i++) {
			num[16-i] = temp[i];
		}
		for (int i = 16; i >=0; i--) {
			sum = sum + a[i]*getan(num,i);
		}
		return sum;
	}

	/**
	 * @param a 数组a储存阶乘
	 */
	private static void GetFactorial(long[] a) {
		// TODO Auto-generated method stub
		for (int i = 0; i < a.length; i++) {
			if (i==0){
				a[0] = 0;
			}else if (i==1) {
				a[1] = 1;
			}else {
				a[i] = a[i-1]*i;
			}
		}
	}

	/**
	 * @param num 输入当前的数组
	 * @param i 
	 * @return 返回i是第几大的数
	 */
	private static long getan(int[] num, int i) {
		// TODO Auto-generated method stub
		int n = 0 ;
		for(int j = i; j>=0; j--){
			if(num[i]>num[j]) n++;
		}
		return n;
	}

}

非常好的问题! ### ❌ `next_permutation` **是** 康托展开。 它们是两种完全同的思想,虽然都和“全排列的字典序”有关,但用途、原理和复杂度完全同。 --- ## ✅ 一、核心区别总结 | 特性 | `next_permutation` | 康托展开(Cantor Expansion) | |------|--------------------|-------------------------------| | 类型 | STL 算法函数 | 数学公式/编码方法 | | 功能 | 生成下一个字典序排列 | 计算某个排列的字典序排名(或反推第k个排列) | | 时间复杂度 | 单次 $O(N)$ | $O(N^2)$ 或 $O(N \log N)$(带树状数组优化) | | 是否需要阶乘? | 否 | 是(预处理 $0!, 1!, ..., (n-1)!$) | | 适用场景 | 小 $M$ 的“后移 M 步”问题 | 快速跳转到第 $k$ 个排列(如 $k=10^9$) | | 能否处理大 $N$? | ✅ 可以(只要 M 小) | ❌ $N > 20$ 时阶乘太大无法存储 | --- ## ✅ 二、详细解释 ### 🔹 1. `next_permutation`:一步一步走 它是一个**迭代算法**,只关心:“当前排列的下一个是谁?” #### 原理步骤: 1. 从右往左找第一个位置 `i`,使得 `a[i] < a[i+1]` 2. 再从右往左找第一个 `j`,使得 `a[j] > a[i]` 3. 交换 `a[i]` 和 `a[j]` 4. 反转 `a[i+1 ... end]` > 这样得到的就是字典序中紧接的下一个排列。 #### 示例: ```cpp [1,2,3,4,5] → [1,2,3,5,4] → [1,2,4,3,5] → ... ``` 每次调用就是“走一步”。 ✅ 优点:简单高效,适合 $M=100$ 这种小步数移动。 ❌ 缺点:能直接跳 $10^9$ 步。 --- ### 🔹 2. 康托展开:数学公式直接定位 康托展开是一种**排列编码系统**,它可以: > 把一个排列映射成一个整数(它的字典序排名) 公式如下: $$ \text{rank} = 1 + \sum_{i=1}^{n} d_i \times (n-i)! $$ 其中 $d_i$ 表示在第 $i$ 位右边且比 $a_i$ 小的数字个数。 #### 举例: 排列 `[2,1,3]` 在所有 3 元排列中排第 3。 - 第1位是2,后面有1个比它小的 → $1 \times 2! = 2$ - 第2位是1,后面没有更小的 → $0 \times 1! = 0$ - 第3位是3 → 参与 - 总计:$2 + 0 + 1 = 3$ 👉 所以它是第3个排列。 #### 逆康托展开: 反过来也可以由排名 $k$ 构造出对应的排列。 ✅ 优点:可以快速求第 $k$ 个排列,比如 $k = 10^9$ ❌ 缺点:当 $n > 20$ 时,$(n-1)!$ 太大,long long 存下! --- ## ✅ 三、对比应用:解决同一道题的同方式 假设我们要解决 P1088,给定排列 A,求它之后第 M 个排列。 | 方法 | 如何做 | 限制 | |------|--------|-------| | `next_permutation` × M 次 | 一步步走 M 步 | 要求 M 很小(≤100) | | 康托展开 + 逆康托展开 | 先算当前排名 → 加 M → 再生成新排列 | 要求 N ≤ 20(否则阶乘溢出) | > ⚠️ 所以对于本题 $N \leq 10000, M \leq 100$,**只能用 `next_permutation`,能用康托展开!** --- ## ✅ 四、形象比喻 | 比喻 | 解释 | |------|------| | `next_permutation` | 像一个人走路:一步一步往前走,每步都知道下一步在哪 | | 康托展开 | 像 GPS 定位:我知道我现在在第几个位置,也能直接导航到第 $k$ 个位置 | 你想从第 100 个排列走到第 103 个? - `next_permutation`:走 3 步 ✔️ - 康托展开:先算自己在哪 → 加 3 → 重建目标排列 ❌(但 N 太大会失败) --- ## ✅ 总结回答你的问题: > ❓ `next_permutation` 是康托展开吗? ### ❌ 是! - `next_permutation` 是一个**构造性算法**,用于生成下一个排列; - 康托展开是一个**数学编码方法**,用于计算排列的排名; - 两者目的同,实现方式同,适用范围也同; - 在本题中,由于 $N$ 太大,**我们只能使用 `next_permutation`,而无法使用康托展开**。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

obession

觉得有用可以打赏咖啡一杯~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值