康拓展开 :

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

其中, 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. 洛谷 P1908 逆序对 Label:归并排序||树状数组 不懂

    题目描述 猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计.最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定 ...

  2. Codeforces 909 C. Python Indentation (DP+树状数组优化)

    题目链接:Python Indentation 题意: Python是没有大括号来标明语句块的,而是用严格的缩进来体现.现在有一种简化版的Python,只有两种语句: (1)'s'语句:Simple ...

  3. Codeforces 909C Python Indentation:树状数组优化dp

    题目链接:http://codeforces.com/contest/909/problem/C 题意: Python是没有大括号来标明语句块的,而是用严格的缩进来体现. 现在有一种简化版的Pytho ...

  4. BZOJ3594: [Scoi2014]方伯伯的玉米田【二维树状数组优化DP】

    Description 方伯伯在自己的农田边散步,他突然发现田里的一排玉米非常的不美. 这排玉米一共有N株,它们的高度参差不齐. 方伯伯认为单调不下降序列很美,所以他决定先把一些玉米拔高,再把破坏美感 ...

  5. bzoj 1106 [POI2007]立方体大作战tet 树状数组优化

    [POI2007]立方体大作战tet Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 821  Solved: 601[Submit][Status][ ...

  6. HDU 6240 Server(2017 CCPC哈尔滨站 K题,01分数规划 + 树状数组优化DP)

    题目链接  2017 CCPC Harbin Problem K 题意  给定若干物品,每个物品可以覆盖一个区间.现在要覆盖区间$[1, t]$. 求选出来的物品的$\frac{∑a_{i}}{∑b_ ...

  7. Codeforces 946G Almost Increasing Array (树状数组优化DP)

    题目链接   Educational Codeforces Round 39 Problem G 题意  给定一个序列,求把他变成Almost Increasing Array需要改变的最小元素个数. ...

  8. HDU - 5542 The Battle of Chibi(LIS+树状数组优化)

    The Battle of Chibi Cao Cao made up a big army and was going to invade the whole South China. Yu Zho ...

  9. Codeforces 629D Babaei and Birthday Cake(树状数组优化dp)

    题意: 线段树做法 分析: 因为每次都是在当前位置的前缀区间查询最大值,所以可以直接用树状数组优化.比线段树快了12ms~ 代码: #include<cstdio> #include< ...

随机推荐

  1. 小技巧(2) 查询自己博客的SEO(如果违规,请先提醒)

    今天,教大家查看自己博客的SEO. 首先,上网站http://seo.chinaz.com/ 接着,在网页输入框中输入自己博客地址(带后面路径,不是cnblogs.com这样的,类似https://w ...

  2. Java Calendar类(java.util包)

    Date 类最主要的作用就是获得当前时间,同时这个类里面也具有设置时间以及一些其他的功能,但是由于本身设计的问题,这些方法却遭到众多批评,不建议使用,更推荐使用 Calendar 类进行时间和日期的处 ...

  3. Java项目之家庭收支记账软件

    模拟实现基于文本界面的家庭记账软件,该软件能够记录家庭的收入支出,并能够打印收支明细表. 项目采用分级菜单方式.主菜单如下: 假设家庭起始的生活基本金为10000元. 每次登记收入(菜单2)后,收入的 ...

  4. CTPN中anchors代码

    import numpy as np def generate_basic_anchors(sizes, base_size=16): #base_anchor([0,0,15,15]) base_a ...

  5. Python Django配置Mysql数据库

    1 在项目中找到setting文件 打开 2 在里面找到 3 将Databases里面的数据改成 DATABASES = { 'default': { #引擎设置为Mysql 'ENGINE': 'd ...

  6. 【PCIE-4】---PCIE中部分概念或问题总结(很基础很重要)

    前面三小节,介绍了PCIE的基本知识和概念,以及扫描流程.在不求甚解的情况下,我想各位小伙伴应该对PCIE有了个宏观的认识,OK,那么本章我们在之前的基础上,再单独把一些概念和更深层次的问题摘出来具体 ...

  7. 【JavaScript 基础知识】一篇关于 JavaScript 一些知识点的总结

    JavaScript 中基础数据类型  数据类型名称  数据类型说明 Undefined 只有一个值,即 undefined ,声明变量的初始值. Null 只有一个值,即 null ,表示空指针,  ...

  8. 2、Automapper安装及配置

    一. 安装 我们安装是在 vs 中使用Nuget的方式进行安装 不过安装时需注意一件事情就是,版本问题,我示例使用的是.net framework 4.5.2,所以我安装AutoMapper的版本是7 ...

  9. msi通过powershell安装、卸载

    function install_msi($url) { $telemetry = @{ DisplayName = "Telemetry Service"; filename = ...

  10. 龙芯 3A4000 Fedora28 安装笔记

    版权声明:原创文章,未经博主允许不得转载 3A4000用起来性能显然已经非常优秀,和朋友手上的3A3000相比有很大的提升(果然网上水分超多的什么测评看看呵呵就好).从零开始却用一半的核数和更低的制程 ...