康拓展开 :

康拓展开,难道他是要飞翔吗?哈哈,当然不是了,康拓具体是哪位大叔,我也不清楚,重要的是
我们需要用到它后面的展开,提到展开,与数学相关的,肯定是一个式子或者一个数进行分解,即
展开。
到底是什么样的式子才配的上这么高大尚的名字?

其中, a[i]为整数,并且0 <= a[i] <= i, 0 <= i < n, 表示当前未出现的的元素中排第几个,这就是康托展开。

就是这样一个神奇的式子,我们发现每项后面都有一个 !, 说明这个式子可能跟阶乘有关,跟阶乘虽然有关,但
跟递归关系可不大哦。 康托展开是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩。
设有n个数(1,2,3,4,…,n),可以有组成不同(n!种)的排列组合.
康托展开表示的就是是当前排列组合在n个不同元素的全排列中的名次

解决哪些问题?

1、全排列问题(数字排列,字符排列,and so on)
2、Hash 函数

康托展开与逆康托展开之间的关系 ?

两者是双射的关系,通俗来讲就是类似乘法和除法的关系,
比如 12345,这样一个数, 我们可以先通过乘法得到,
0 * 10 + 5 + 4 * 10 + 4 + 3 * 100 + 2 * 1000 + 1 * 10000;
同时,我们得到这个整数后也可以通过 / 10 与 % 10 进行拆分成每一个
数。(可能解释的不是十分到位,可以自己再琢磨琢磨)

康托展开怎么求呢 ?

1、通过知道一个排列,然后可以得到这个排列在全排列中的排名。
具体来说就是 : 比如 1234 ,那么 1243 再由这 4 个数中
组成的排名 是 2 (1234 如果是排名 1 的话,实际上康托展开
的排名是从 0 开始的,所以我们最后的排名的时候习惯 + 1,为
啥会是从 0 开始的,下面会进行详细的解释) 2、康托展开怎么求呢 ?
在(1,2,3,4,5)5个数的排列组合中,计算 34152的康托展开值。
首位是3,则小于3的数有两个,为1和2,a[5]=2,则首位小于3的所有排列组合为 a[5]*(5-1)!
第二位是4,则小于4的数有两个,为1和2,注意这里3并不能算,因为3已经在第一位,所以其实计算的是在第二位之后小于4的个数。因此a[4]=2
第三位是1,则在其之后小于1的数有0个,所以a[3]=0
第四位是5,则在其之后小于5的数有1个,为2,所以a[2]=1
最后一位就不用计算啦,因为在它之后已经没有数了,所以a[1]固定为0
根据公式:
X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 2 * 24 + 2 * 6 + 1 = 61
所以比 34152 小的组合有61个,即34152是排第62。

逆康托展开怎么求呢 ?

在(1,2,3,4,5)给出61可以算出起排列组合为 34152。由上述的计算过程可以容易的逆推回来,具体过程如下:
用 61 / 4! = 2余13,说明a[5]=2,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明a[4]=2,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明a[3]=0,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明a[2]=1,说明在第二位之后小于第四位的数有1个,所以第四位为5。
最后一位自然就是剩下的数2啦。
通过以上分析,所求排列组合为 34152。

为什么康托展开的排名是从 0 开始的 ?

那 1234 来说:
1234 的康托展开为 : 0 * 3! + 0 * 2! + 0 * 1! + 0 * 0! = 0
(比 1 小的数有 0 个, 1 用掉后, 比 2 小的数也剩下 0 个,后面依次类推,得到的值是 0)
所以排名是从 0 开始的,但是我们说排名时习惯从 1 开始,所以会在最后序列得出的时候 + 1.

应用:

1、给定一个自然数集合组合一个全排列,所其中的一个排列组合在全排列中从小到大排第几位。
在上述例子中,在(1,2,3,4,5)的全排列中,34152的排列组合排在第62位。 2、反过来,就是逆康托展开,求在一个全排列中,从小到大的第n个全排列是多少。
比如求在(1,2,3,4,5)的全排列中,第62个排列组合是34152。[注意具体计算中,要先 -1 才是其康托展开的值。] 3、另外康托展开也是一个数组到一个数的映射,因此也是可用于hash,用于空间压缩。比如在保存一个序列,
我们可能需要开一个数组,如果能够把它映射成一个自然数, 则只需要保存一个整数,大大压缩空间。比如八数码问题。

参考链接:

wbin233 大佬 : https://blog.csdn.net/wbin233/article/details/72998375

Code:

康托展开:

// 阶乘
void init_fac() {
fac[0] = 1;
for(int i = 1; i <= 12; i ++) {
fac[i] = fac[i - 1] * i;
}
} LL cantor() {
int len = strlen(a);
for(int i = 0; i < len; i ++) {
LL temp = 0;
for(int j = i + 1; j < len; j ++) {
// 找到所有未使用过的数中比当前数小的个数
if(a[j] < a[i]) temp ++;
}
res += temp * fac[len - i - 1];
}
return res + 1; // 返回排名(是从 0 开始的,所以需要 + 1)
}

树状数组优化:

/*
为什么用树状数组(线段树也可以)?
1、求前缀和
2、支持在线修改
我们可以通过预处理求取前缀和,每个数比它小的就是数量就是其 本身的值 - 1
树状数组经过预处理后 C 数组可能是这样的 :
1 2 3 4 5 6 7
那么比 3 小的个数有几个呢?实际上就是本身的前缀和 - 1即可
*/ #include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> #define MOD 998244353 using namespace std; typedef long long LL;
const int maxn = 1e6 + 10; int a[maxn],c[maxn];
LL fac[maxn]; LL res = 0;
int n; int main(void) {
void add(int x,int v);
int query(int x);
void init_fac();
LL cantor();
scanf("%d",&n);
init_fac();
for(int i = 1; i <= n; i ++) {
scanf("%d",&a[i]);
// - 1 是因为排名是从 0 开始的
res = (res + query(a[i] - 1) * fac[n - i] );
add(a[i],-1); // 这个数用掉过后就不能用了,所以它后面比它大(比这些大的的小的 的数量就会 -1)
// 可能有点绕, 比如 1 2 3 4 5,用掉 2 后,后面比 3 小的数量就减少了一个
}
cout << res + 1 << endl;
return 0;
} int lowbit(int x) {
return x & -x;
} void add(int x,int v) {
for(int i = x; i <= n; i += lowbit(i)) {
c[i] += v;
}
return ;
}
void init_fac() {
fac[0] = 1;
for(int i = 1; i <= n; i ++) {
fac[i] = (fac[i - 1] * i);
add(i,1); // 求前缀和
}
return;
}
int query(int x) {
int res = 0;
for(int i = x; i ; i -= lowbit(i)) {
res += c[i];
}
return res;
}

逆康托展开:

void decantor(int x, int n)
{
vector<int> v; // 存放当前可选数
vector<int> a; // 所求排列组合
for(int i=1;i<=n;i++)
v.push_back(i);
for(int i=m;i>=1;i--)
{
int r = x % FAC[i-1];
int t = x / FAC[i-1];
x = r;
sort(v.begin(),v.end());// 从小到大排序
a.push_back(v[t]); // 剩余数里第t+1个数为当前位
v.erase(v.begin()+t); // 移除选做当前位的数
}
}

树状数组 + 二分 优化:


LL Check(LL l,LL r,LL aim) {
while(l < r) {
LL mid = (l + r) >> 1;
if(query(mid) - 1 >= aim) {
r = mid;
} else {
l = mid + 1;
}
}
return r;
} void reCantor() {
pos --; // 需要先 -- (排名从 0 开始)
for(LL i = 1,num = n - 1; i <= n; num --, i ++) {
LL aim = pos / fac[num]; // 找 第 k 大的数
pos = pos % fac[num];
LL t = Check(1,n,aim);
cout << t << " ";
add(t ,-1); // 与上面的解释一样
}
cout << endl;
return ;
}

例题 :

1、Dictionary:

https://vjudge.net/problem/CSU-1828#author=0

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; const int maxn = 12; typedef long long LL; char str[maxn];
LL fac[maxn];
LL res = 0;
int T; int main(void) {
LL kangtuo();
void init_fac();
init_fac();
scanf("%d",&T);
while(T --) {
res = 0;
cin >> str;
res = kangtuo();
cout << res << endl;
}
return 0;
} void init_fac() {
fac[1] = 1;
for(int i = 2; i <= 12; i ++) {
fac[i] = fac[i - 1] * i;
}
return ;
} LL kangtuo() {
int len = strlen(str);
for(int i = 0; i < len; i ++) {
LL temp = 0;
for(int j = i + 1; j < len; j ++) {
if(str[j] < str[i]) temp ++;
}
res += temp * fac[len - i - 1];
}
return res + 1;
}

2、Uim的情人节礼物·其之弐

https://www.luogu.com.cn/problem/P2524

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; const int maxn = 10; typedef long long LL; LL fac[maxn];
char a[maxn]; int n;
LL res = 0;
int main(void) {
void init_fac();
LL cantor();
init_fac();
cin >> n >> a;
res = 0;
res = cantor();
cout << res << endl;
return 0;
} void init_fac() {
fac[0] = 1;
for(int i = 1; i <= 12; i ++) {
fac[i] = fac[i - 1] * i;
}
} LL cantor() {
int len = strlen(a);
for(int i = 0; i < len; i ++) {
LL temp = 0;
for(int j = i + 1; j < len; j ++) {
if(a[j] < a[i]) temp ++;
}
res += temp * fac[len - i - 1];
}
return res + 1;
}

3、【模板】康托展开(树状数组优化)

https://www.luogu.com.cn/problem/P5367

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> #define MOD 998244353 using namespace std; typedef long long LL;
const int maxn = 1e6 + 10; int a[maxn],c[maxn];
LL fac[maxn]; LL res = 0;
int n; int main(void) {
void add(int x,int v);
int query(int x);
void init_fac();
LL cantor();
scanf("%d",&n);
init_fac();
for(int i = 1; i <= n; i ++) {
scanf("%d",&a[i]);
res = (res % MOD + ((query(a[i] - 1)) % MOD * fac[n - i] % MOD) % MOD );
add(a[i],-1); // 用过的就不能再用了
}
cout << res + 1 << endl;
return 0;
} int lowbit(int x) {
return x & -x;
} void add(int x,int v) {
for(int i = x; i <= n; i += lowbit(i)) {
c[i] += v;
}
return ;
}
void init_fac() {
fac[0] = 1;
for(int i = 1; i <= n; i ++) {
fac[i] = (fac[i - 1] % MOD * i % MOD)% MOD;
add(i,1);
}
return;
} int query(int x) {
int res = 0;
for(int i = x; i ; i -= lowbit(i)) {
res += c[i];
}
return res;
}

4、[USACO11FEB]牛线Cow Line:

https://www.luogu.com.cn/problem/P3014

(康托展开 & 逆康托展开的综合应用)

(坑不少,卡long long)

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; typedef long long LL; const int maxn = 100; LL fac[maxn],c[100]; // 全部用 long long
LL a[maxn];; LL res = 0;
LL T,n,pos;
char op; int main(void) {
LL cantor();
void add(LL x,LL v);
void reCantor();
void init_fac();
scanf("%lld%lld",&n,&T);
while(T --) {
cin >> op;
init_fac();
if(op == 'Q') {
memset(c,0,sizeof(c));
for(LL i = 1; i <= n; i ++) {
scanf("%lld",&a[i]);
add(i,1);
}
res = 0;
res = cantor();
cout << res << endl;
} else {
memset(c,0,sizeof(c));
for(LL i = 1; i <= n; i ++) {
add(i,1);
}
scanf("%lld",&pos);
reCantor();
}
}
return 0;
}
LL lowbit(LL x) {
return x & -x;
} void add(LL x,LL v) {
for(LL i = x; i <= n; i += lowbit(i)) {
c[i] += v;
}
return ;
}
void init_fac() {
fac[0] = 1;
for(LL i = 1; i <= n; i ++) {
fac[i] = fac[i - 1] * i;
}
return ;
} LL query(LL x) {
LL res = 0;
for(LL i = x; i ; i -= lowbit(i)) {
res += c[i];
}
return res;
} LL cantor() {
LL res = 0;
for(LL i = 1; i <= n; i ++) {
res += (query(a[i] - 1) * fac[n - i]);
add(a[i],-1);
}
return res + 1; // 不要忘记 + 1
} LL Check(LL l,LL r,LL aim) {
while(l < r) {
LL mid = (l + r) >> 1;
if(query(mid) - 1 >= aim) {
r = mid;
} else {
l = mid + 1;
}
}
return r;
} void reCantor() {
pos --;
for(LL i = 1,num = n - 1; i <= n; num --, i ++) {
LL aim = pos / fac[num]; // 找 第 k 大的数
pos = pos % fac[num];
LL t = Check(1,n,aim);
cout << t << " ";
add(t ,-1);
}
cout << endl;
return ;
}

最新文章

  1. android开发中fragment获取context
  2. Selenium简介(二)--基于CORE/IDE的简单应用
  3. Reporting Service 配置SMTP和设置订阅出现的异常
  4. js-FCC算法Smallest Common Multiple。找出两个参数和它们之间的连续数字的最小公倍数。
  5. mysqldump备份详解
  6. [Ruby on Rails系列]4、专题:Rails应用的国际化[i18n]
  7. 【STL学习】智能指针之shared_ptr
  8. 【DSA MOOC】起泡排序的原理及常数优化
  9. web前端-雅虎34条规则优化
  10. JAVA中浅复制与深复制 - coolmist - ITeye技术网站
  11. PHP如何与搜索引擎Elasticsearch交互?
  12. SpringEL 表达式错误记录
  13. 导入android SlidingMenu 应用
  14. MUI上传图片之选择相册和相机上传
  15. 【页面加载】【九九乘法表】【document.write的功能_】【&lt;script&gt;直接显示数组】【声明新变量】
  16. 设计模式---对象创建模式之原型模式(prototype)
  17. scrapy--分布式爬虫
  18. Android调用系统的打电话和发短信界面(1.将消息内容带过去2.实现群发)
  19. nodeSelector + deamonset
  20. 【设计模式】—— 解释器模式Interpret

热门文章

  1. POJ 3304 Segments(判断直线与线段是否相交)
  2. curl使用post方式访问Spring Cloud gateway报time out错误
  3. HashMap 源码赏析 JDK8
  4. js实现类选择器和name属性选择器
  5. 【转】Java Web Services面试问题集锦
  6. vue 移动端在div上绑定click事件 失效
  7. Spark 配置参数
  8. 两个关于 Java 面试的 Github 项目
  9. cogs 647. [Youdao2010] 有道搜索框 Trie树 字典树
  10. 测试工具Fiddler(二)—— 入门使用