阶乘进制 (Factorial Number System) 原理介绍
阶乘进制,又称为 Lehmer 码 (Lehmer code),是一种混合基数系统 (Mixed Radix System),它使用阶乘作为每一位的权值(基数),而不是像传统的 进制(如十进制、二进制)那样使用
的幂作为权值。
1. 核心思想
阶乘进制的核心思想是:任何一个非负整数 都可以唯一地表示为一系列系数乘以对应阶乘之和。
2. 表示形式
一个非负整数 在阶乘进制下可以表示为:
其中,每一位 的权值(基数)是
(
的阶乘)。
数学表达式为:
3. 系数(数字)的限制
这是阶乘进制最关键的特征之一。每一位上的系数 都有严格的取值范围,它取决于该位的权值
:
| 权值 ( |
位的索引 ( |
系数 ( |
|---|---|---|
注意: 最低位 的权值是
,其系数
只能取
(因为
)。所以,在实际表示中,阶乘进制的最低位
常常被省略或约定为
。
4. 应用:排列和 Lehmer 码
阶乘进制最著名的应用是在排列 (Permutations) 领域:
- 对于
个元素的集合,总共有
种不同的排列方式。
- 阶乘进制的
范围内的每一个数,都唯一对应一个
个元素的排列。这个数就是该排列的 Lehmer 码。
- 系数
在排列中代表了在当前剩下的元素中,第
个位置的元素是第
小的元素(基于 0-索引)。
简而言之,阶乘进制提供了一种将排列和整数进行双射(一一对应)编码的方法。
算法思路
直接使用字典序枚举 N! 种排列在 N=20 时是不可能的。
取而代之的是 阶乘进制(Factorial Number System,亦称 Lehmer 码)。
根据上述讨论过的阶乘进制:
个数的排列总共有
种。
- 通过阶乘进制,我们可以将任意一个排列映射为一个唯一的整数(排名),反之亦然。
1. 预处理阶乘
由于 ,我们需要预先计算
到
的值。
注意:
,这在 C++ 中可以用
unsigned long long (64位无符号整数) 存储(最大约 )。
2. 操作 P:已知排名找排列(逆康托展开)
输入是 1-based 的排名 ,首先将其转化为 0-based(即
)。
我们维护一个可用数字列表(初始为
)。
从第 1 位到第 位,对于第
位(从 0 开始计数):
- 该位的权值是
。
- 计算系数:
。
- 这个系数
表示我们需要从当前剩余的可用数字列表中取出第
个数字(0-索引)。
- 输出该数字,并从列表中删除它。
- 更新排名:
。
3. 操作 Q:已知排列找排名(康托展开)
我们需要计算有多少个排列的字典序小于当前给定的排列。
结果初始为 。
对于排列中的每一个数 (位置记为
,从 0 开始):
- 查看在这个数之后的位置(或者简单的说,在未被使用的数字集合中),有多少个数字比
小。记为
。
- 该位对排名的贡献为
。
。
- 标记
为已使用。
最后输出 (转回 1-based 排名)。
代码实现
#include <iostream>
#include <vector>
#include <array>
#include <algorithm>
using namespace std;
using ll = unsigned long long;
// 使用 unsigned long long 防止溢出,20! 约为 2.4e18,而 unsigned long long 的最大值约为 1.8e19 (即 2^64−1)。
array<ll, 21> fact;
// 预处理阶乘
void precompute() {
fact[0] = 1;
for (int i = 1; i <= 20; i++) {
fact[i] = fact[i - 1] * i;
}
}
// 操作 P: 根据排名找排列 (逆康托展开)
void solveP(int n, long long rank) {
rank--; // 将 1-based 排名转换为 0-based
vector<int> available;
// 初始化可用数字列表 1 到 N
for (int i = 1; i <= n; i++) available.push_back(i);
for (int i = 0; i < n; i++) {
ll f = fact[n - 1 - i]; // 当前位的阶乘权值
int idx = rank / f; // 确定选第几个剩下的数
rank %= f; // 更新 rank 为余数
// 输出选中的数
cout << available[idx] << (i == n - 1 ? "" : " ");
// 从可用列表中移除该数
available.erase(available.begin() + idx);
}
cout << endl;
}
// 操作 Q: 根据排列找排名 (康托展开)
void solveQ(int n, vector<int>& perm) {
ll rank = 0;
// 用于标记数字是否已经出现过
vector<bool> used(n + 1, false);
for (int i = 0; i < n; i++) {
int val = perm[i];
int smaller_count = 0;
// 统计有多少个比当前数小且未被使用过的数
// 因为 N 最大才 20,直接循环统计即可,复杂度 O(N^2) 对每组查询都很快
for (int j = 1; j < val; j++) {
if (!used[j]) smaller_count++;
}
// 累加当前位的贡献
rank += smaller_count * fact[n - 1 - i];
used[val] = true;
}
// 输出 1-based 排名
cout << rank + 1 << endl;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
precompute();
int N, K;
if (cin >> N >> K) {
for (int k = 0; k < K; k++) {
char type;
cin >> type;
if (type == 'P') {
long long r;
cin >> r;
solveP(N, r);
} else {
vector<int> perm(N);
for (int i = 0; i < N; i++) {
cin >> perm[i];
}
solveQ(N, perm);
}
}
}
return 0;
}

京公网安备 11010502036488号