数论(7):康托展开&逆康托展开
康托展开可以用来求一个
什么是排列的排名?
把
时间复杂度?
康托展开可以在
怎么实现?
因为排列是按字典序排名的,因此越靠前的数字优先级越高。也就是说如果两个排列的某一位之前的数字都相同,那么如果这一位如果不相同,就按这一位排序。
比如
举个栗子
我们知道长为
按照这样统计下去,答案就是
注意到我们每次要用到 当前有多少个小于它的数还没有出现 ,这里用树状数组统计比它小的数出现过的次数就可以了。
原理总结:
- 注意到我们每次要用到 当前有多少个小于它的数还没有出现
- 这里用树状数组统计比它小的数出现过的次数就可以了,可以优化到
O(nlogn)
代码
先预处理阶乘:
void init(){
fact[0] = 1;
for(int i = 2;i <= 9; ++i) fact[i] = fact[i - 1] * i;
//递推求阶乘
}
//或者直接打表
int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
int cantor(int a[],int n){
int ans = 0;
for(int i = 0;i < n; ++i){
int cnt = 0;
for(int j = i + ;j < n; ++j)++cnt;
// 找到a[i]是当前数列中未出现的数中第几小的
// 从1开始,即1-n的全排列
// 从0开始,就变成了0-n的全排列,记得变通
ans += cnt * fact[n - i - 1];
}
return ans + 1;//如果输出的是排名就要 + 1,如果是hash值可以直接返回 ans
}
逆康托展开
而逆康托展开相当于,反过来操作
因为排列的排名和排列是一一对应的,所以康托展开满足双射关系,是可逆的。可以通过类似上面的过程倒推回来。
如果我们知道一个排列的排名,就可以推出这个排列。因为
引用上面展开的例子
首先让
此时让排名减去
让
实际上我们得到了形如 有两个数小于它 这一结论,就知道它是当前第
代码
vector<int> incantor(int x,int n){
x--;//得到从0开始的排名
vector<int> res(n); //保存数列答案
int cnt;
bool st[10]; //标记数组
memset(st,false,sizeof st);
for(int i = 0;i < n; ++i){
cnt = x / fact[n - i - 1]; // 比a[i]小且没有出现过的数的个数
x %= fact[n - i - 1]; //更新 x
for(int j = 1; j <= n; ++j){// 找到a[i],从1开始向后找
if(st[j]) continue; // 如果被标记过,就跳过
if(!cnt){ // 如果cnt == 0说明当前数是a[i]
st[j] = 1; //标记
res[i] = j; // 第i位是j
break;
}
cnt--; // 如果当前不是0,就继续往后找
}
}
return res;// 返回答案
}
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库
· 5. Nginx 负载均衡配置案例(附有详细截图说明++)