当前位置: 首页 > news >正文

康托展开与逆康托展开

康托展开

康托展开是用于求一个 排列全排列 中的 位次 的算法。

a=52413a=52413a=52413 为例:

  • a1a_1a1555,对于一个排列 bbb 而言,当 b1∈{1,2,3,4}b_1\in\{1,2,3,4\}b1{1,2,3,4} 的时候有 b<ab<ab<a,这样的 bbb4×4!4\times 4!4×4! 个。
  • a2a_2a2222,对于一个排列 bbb 而言,当 b1=a1b_1=a_1b1=a1b2=1b_2=1b2=1 时有 b<ab<ab<a,这样的 bbb1×3!1\times 3!1×3! 个。
  • a3a_3a3444,对于一个排列 bbb 而言,前缀与 aaa 相同,当 b3∈{1,3}b_3\in\{1,3\}b3{1,3} 时,有 b<ab<ab<a,这样的 bbb2×2!2\times 2!2×2! 个。
  • a4a_4a4111,对于一个排列 bbb 而言,前缀与 aaa 相同,不存在 b<ab<ab<a
  • a5a_5a5333,对于一个排列 bbb 而言,前缀与 aaa 相同,不存在 b<ab<ab<a

所以 aaa 的位次相当于求有多少个 bbb 小于 aaa,可知有 106106106 个排列小于 aaa,所以 aaa 的位次是 107107107

总结一下求位次的过程就是:

  • 对于当前位置 aia_iai,求 出剩下可用的数有多少个小于 aia_iai,设为 cntcntcnt
  • 对于当前位置 aia_iai,求出 后面还有几个数没排,设为 bntbntbnt
  • 累加答案 cnt×bnt!cnt\times bnt!cnt×bnt!
  • 从剩下的可用的数中删去 aia_iai,并前往下一个位置。

可以发现 bnt!bnt!bnt! 是可以预处理的,现在的问题就是如何动态维护剩下的数,且能查询出有多少个数小于 aia_iai

因为 ai≤na_i\le nain,所以可以使用 权值树状数组 求解。

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int MOD = 998244353;
const int N = 1e6 + 7;
template<typename T>// 单点修改和区间查询的树状数组
struct Fenwick {vector <T> tree;int n;Fenwick(int _n) : n(_n), tree(_n + 2){}// 求 1~x 的前缀和T getsum(int x) {T ans = 0;int L = x;while (x) {ans += tree[x];x = x - lowbit(x);}return ans;}// 区间查询T get_range_sum(int l, int r) {return getsum(r) - getsum(l - 1);}// 单点修改void add(int x, T k) {while (x <= n) {tree[x] += k;x += lowbit(x);}}static inline int lowbit(int x) {return x & (-x);}T getKth(int k) {T x = 0, sum = 0;for (int i = log2(n); i >= 0; i--) {x += (1ll << i);if (x >= n || sum + tree[x] >= k) {x -= (1ll << i);} else {sum += tree[x];}}return x + 1;}
};int fac[N];
void init(int n) {fac[0] = 1;for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % MOD;
}int Cantor(int n, vector <int> &p) {Fenwick<int> tree(n);for (int i = 1; i <= n; i++) {tree.add(i, 1);}int ans = 0;for (int i = 1; i <= n; i++) {int cnt = tree.get_range_sum(1, p[i] - 1);int bnt = fac[n - i];ans = (ans + cnt * bnt % MOD) % MOD;tree.add(p[i], -1);}return ans;
}
void slove () {int n;cin >> n;vector <int> p(n + 1);for (int i = 1; i <= n; i++) cin >> p[i];init(n);int ans = Cantor(n, p);cout << ans + 1 << endl;
}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();
}

逆康托展开

当我们知道了一个排列的位次,如何求出具体排列?

根据康托展开,我们可以知道一个排列的位次公式:
a1×(n−1)!+a2×(n−2)!+⋯+an×0! a_1\times(n-1)!+a_2\times(n-2)!+\cdots +a_n\times 0! a1×(n1)!+a2×(n2)!++an×0!
下面给出求出具体排列的步骤:

设当前位次是 pospospos,当前要确定的是排列的第 iii 项,

  • 判断 pospospos 是否大于等于 (n−i)!(n-i)!(ni)!
    • 求出系数 k=pos/(n−i)!k=pos/(n-i)!k=pos/(ni)!,说明 当前剩下的数里有 kkk 个小于 a[i]a[i]a[i],那么 a[i]a[i]a[i] 就是剩下的数里的第 k+1k+1k+1 小的数。
  • pospospos(n−i)!(n-i)!(ni)! 取模
  • 接下来确定第 i+1i+1i+1 项的值。

因为我们需要进行除法、取模的操作,所以不能再模 ModModMod 的意义下出现,这也意味着 n!≤264−1n!\le 2^{64}-1n!2641

否则就会爆 longlonglong longlonglong

可以发现阶乘是可以预处理的,然后就是维护剩下的数,并 查询第 k+1k+1k+1 小的数,这个可以用 权值树状数组 来求解。

下述代码是包含康托展开和逆康托展开的代码,值得注意的是,如果给定某一排列的位次,进行逆康托展开的时候,记得将位次减 111,因为我们需要的是小于该排列的数量。

同理,进行康托展开的时候,把答案加 111,因为求出来的是小于当前排列的数量。

切记不要取模

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int MOD = 998244353;
const int N = 1e6 + 7;
template<typename T>// 单点修改和区间查询的树状数组
struct Fenwick {vector <T> tree;int n;Fenwick(int _n) : n(_n), tree(_n + 2){}// 求 1~x 的前缀和T getsum(int x) {T ans = 0;int L = x;while (x) {ans += tree[x];x = x - lowbit(x);}return ans;}// 区间查询T get_range_sum(int l, int r) {return getsum(r) - getsum(l - 1);}// 单点修改void add(int x, T k) {while (x <= n) {tree[x] += k;x += lowbit(x);}}static inline int lowbit(int x) {return x & (-x);}// 求区间第 k  小T getKth(int k) {T x = 0, sum = 0;for (int i = log2(n); i >= 0; i--) {x += (1ll << i);if (x >= n || sum + tree[x] >= k) {x -= (1ll << i);} else {sum += tree[x];}}return x + 1;}
};int fac[N];
void init(int n) {fac[0] = 1;for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i;
}// 下标从 1 开始
int Cantor(int n, vector <int> &p) {Fenwick<int> tree(n);for (int i = 1; i <= n; i++) {tree.add(i, 1);}int ans = 0;for (int i = 1; i <= n; i++) {int cnt = tree.get_range_sum(1, p[i] - 1);int bnt = fac[n - i];ans = (ans + cnt * bnt);tree.add(p[i], -1);}return ans;
}vector <int> AntiCantor(int n, int pos) {vector <int> p (n + 1);Fenwick<int> tree(n);for (int i = 1; i <= n; i++) {tree.add(i, 1);}for (int i = 1; i <= n; i++) {int k = pos/fac[n - i];pos %= fac[n - i];p[i] = tree.getKth(k + 1);tree.add(p[i], -1);}return p;
}void slove () {int n, q;cin >> n >> q;init(n);while (q--) {char op;cin >> op;if (op == 'P') {int pos;cin >> pos;vector <int> p = AntiCantor(n, pos - 1);for (int i = 1; i <= n; i++) cout << p[i] << ' ';cout << endl;} else {vector <int> p(n + 1);for (int i = 1; i <= n; i++) cin >> p[i];cout << Cantor(n, p) + 1 << endl;}}}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();
}
http://www.dtcms.com/a/330308.html

相关文章:

  • 词向量转化
  • RocketMQ 消息存储机制 CommitLog和ConsumerQu
  • 第八课:python的运算符
  • 从 VLA 到 VLM:低延迟RTSP|RTMP视频链路在多模态AI中的核心角色与工程实现
  • 论文分享 | Flashboom:一种声东击西攻击手段以致盲基于大语言模型的代码审计
  • 04-spring-手写spring-demo-aop0V1
  • Canal解析MySQL Binlog原理与应用
  • Unity、C#常用的时间处理类
  • Laravel 使用ssh链接远程数据库
  • 使用 Simple Floating Menu 插件轻松实现浮动联系表单
  • AI一周事件(2025年8月6日-8月12日)
  • [ Mybatis 多表关联查询 ] resultMap
  • ResourcelessTransactionManager的作用
  • 第三天-如何在DBC中描述CAN Signal的“负数/值”
  • JetPack系列教程(六):Paging——让分页加载不再“秃”然
  • 理财学习资料推荐
  • 谈一些iOS组件化相关的东西
  • C# 多线程:并发编程的原理与实践
  • C++中的STL标准模板库和string
  • Heterophily-aware Representation Learning on Heterogeneous Graphs
  • AI - 工具调用
  • AI智能体记忆策略
  • 10 ABP 模块系统
  • [转]SURREAL数据集国内下载链接
  • Deep Agents:用于复杂任务自动化的 AI 代理框架
  • nm命令和nm -D命令参数
  • 19. 重载的方法能否根据返回值类型进行区分
  • Java之String类
  • 3.Cursor提效应用场景实战
  • UEdior富文本编辑器接入AI