康拓展开 & 逆康拓展开 知识总结(树状数组优化)
康拓展开 :
康拓展开,难道他是要飞翔吗?哈哈,当然不是了,康拓具体是哪位大叔,我也不清楚,重要的是
我们需要用到它后面的展开,提到展开,与数学相关的,肯定是一个式子或者一个数进行分解,即
展开。
到底是什么样的式子才配的上这么高大尚的名字?
其中, 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 ;
}
康拓展开 & 逆康拓展开 知识总结(树状数组优化)的更多相关文章
- 洛谷 P1908 逆序对 Label:归并排序||树状数组 不懂
题目描述 猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计.最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定 ...
- Codeforces 909 C. Python Indentation (DP+树状数组优化)
题目链接:Python Indentation 题意: Python是没有大括号来标明语句块的,而是用严格的缩进来体现.现在有一种简化版的Python,只有两种语句: (1)'s'语句:Simple ...
- Codeforces 909C Python Indentation:树状数组优化dp
题目链接:http://codeforces.com/contest/909/problem/C 题意: Python是没有大括号来标明语句块的,而是用严格的缩进来体现. 现在有一种简化版的Pytho ...
- BZOJ3594: [Scoi2014]方伯伯的玉米田【二维树状数组优化DP】
Description 方伯伯在自己的农田边散步,他突然发现田里的一排玉米非常的不美. 这排玉米一共有N株,它们的高度参差不齐. 方伯伯认为单调不下降序列很美,所以他决定先把一些玉米拔高,再把破坏美感 ...
- bzoj 1106 [POI2007]立方体大作战tet 树状数组优化
[POI2007]立方体大作战tet Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 821 Solved: 601[Submit][Status][ ...
- HDU 6240 Server(2017 CCPC哈尔滨站 K题,01分数规划 + 树状数组优化DP)
题目链接 2017 CCPC Harbin Problem K 题意 给定若干物品,每个物品可以覆盖一个区间.现在要覆盖区间$[1, t]$. 求选出来的物品的$\frac{∑a_{i}}{∑b_ ...
- Codeforces 946G Almost Increasing Array (树状数组优化DP)
题目链接 Educational Codeforces Round 39 Problem G 题意 给定一个序列,求把他变成Almost Increasing Array需要改变的最小元素个数. ...
- 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 ...
- Codeforces 629D Babaei and Birthday Cake(树状数组优化dp)
题意: 线段树做法 分析: 因为每次都是在当前位置的前缀区间查询最大值,所以可以直接用树状数组优化.比线段树快了12ms~ 代码: #include<cstdio> #include< ...
随机推荐
- kubespy 用bash实现的k8s动态调试工具
原文位于 https://github.com/huazhihao/kubespy/blob/master/implement-a-k8s-debug-plugin-in-bash.md 背景 Kub ...
- Ubuntu1804下安装Gitab
部署gitlab 1.配置仓库源 # vim /etc/apt/sources.listdeb http://mirrors.aliyun.com/ubuntu/ bionic main restri ...
- (数据科学学习手札72)用pdpipe搭建pandas数据分析流水线
1 简介 在数据分析任务中,从原始数据读入,到最后分析结果出炉,中间绝大部分时间都是在对数据进行一步又一步的加工规整,以流水线(pipeline)的方式完成此过程更有利于梳理分析脉络,也更有利于查错改 ...
- enum sizeof typedef分析
1.枚举类型的使用方法 enum是C语言中的一种自定义类型 enum值是可以根据需要自定义的整型值 第一个定义的enum值默认为0 默认情况下的enum值是在前一个定义值的基础上加1 enum类型的变 ...
- MySql查看修改l时区
# 查看时区 show variables like '%time_zone%'; # 设置全局 set global time_zone='+8:00'; # 设置当前会话 set time_zo ...
- (转) XSS Attacks – Exploiting XSS Filter
中文翻译: from wooyun'drops 0x00 前言 这又是一篇来自全职赏金猎人Masato kinugawa的神作.一次双杀,用一篇报告拿下了两个CVE,分别是CVE-2015-6144和 ...
- python练习题-9-6
本代码是<Python核心编程(第二版)>第九章的9-6练习题,完成的功能为:文件比较:比较两个文本文件是否相同,如果不同,给出第一个不同处的行号和列号. #!/usr/bin/env p ...
- 趣学CCNA 路由与交换
第1章 OSI和TCP/IP 11.1 协议与协议分层 31.2 OSI参考模型 61.2.1 物理层 91.2.2 数据链路层 91.2.3 网络层 101.2.4 传输层 101.2.5 会话层 ...
- 基于Bootstrap和Knockout.js的ASP.NET MVC开发实战 关于 拦截器的 学习 部分
先贴一段: 下面贴代码: 上面这段代码呢,有几个点迷糊.可以找找看
- python多层数组合成一个数组后循环打印出数组内的每一项元素的方法
a=[1,2,3,4] b=[5,6,7,8] c=[a,b] def test(c): for i in c: return i print(test(c)) 以上代码执行后打印出来的结果是 预期目 ...