Note - 康托展开

1.用途#

康托展开可以用来求一个 1n 的任意排列的排名。是一个很好的 hash 方法。

2.算法介绍#

时间复杂度

普通的康托展开可以 O(n2) 的复杂度内求出排名,加上树状数组优化后则可以 O(nlogn)

实现

对于一个排列 a , 统计 ai 后面比它小的数字的个数 num ,这 num 表示着有 num 各数在它前面,而在排列里面,每级别都是阶乘级的,所以答案就累加 num×facni

用数学公式表示就是:

i=1n1facni×j=i+1n1 (if aj<ai)

这样的负责度为 O(n2)

我们发现后面的 j=i+1n1 (if aj<ai) 可以用树状数组来维护。于是时间复杂度降到了 O(nlogn)

代码

未优化

void contor(int a[]) {
	int num = 0, ans = 0;
	for (int i = 1; i < n; i++) {
		for (int j = i + 1; j <= n; j++) {
			if (a[j] < a[i]) {
				num++;
			}
		}
		ans += num * fac[n - i];
		num = 0;
	}
	return ans + 1;
} 

优化(附树状数组板子)

int c[maxn];
int lowbit(int x) {
	return x & -x;
}
void add(int x, int k) {
	for (; x <= n; x += lowbit(x)) {
		c[x] += k;
	}
}
int ask(int x) {
	int ans = 0;
	for (; x; x -= lowbit(x)) {
		ans += c[x];
	}
	return ans;
}
void contor(int a[]) {
	for (int i = 1; i <= n; i++) add(a[i], 1);
	int ans = 0;
	for (int i = 1; i < n; i++) {
        int num = ask(a[i] - 1);
        add(a[i], -1);
        ans += sum * fac[n - i];
    }
	return ans + 1;
} 

3.逆康托展开#

举个例子就很清晰啦~
引用自 康托展开 - OI WIKI

首先让 461=4545 代表着有多少个排列比这个排列小。454!=1,有一个数小于它,所以第一位是 2

此时让排名减去 1×4 得到 21213!=3 ,有 3 个数小于它,去掉已经存在的 2,这一位是 5

213×3!=332!=1,有一个数小于它,那么这一位就是 3

31×2!=1 ,有一个数小于它,这一位是剩下来的第二位,4 ,剩下一位就是 1 。即 [2,5,3,4,1]

实际上我们得到了形如 有两个数小于它 这一结论,就知道它是当前第 个没有被选上的数,这里也可以用线段树维护,时间复杂度为 O(nlogn)

Code. (未优化)

bool vis[maxn];
vector<int> reverse_contor(int x) {
	x--;
	vector<int> ans;
	for (int i = 1; i <= n; i++) {
		int t = n / fac[i];
		for (int j = 1; j <= n; j++) {
			if (!vis[j]) {
				if (!t) {
					vis[j] = true;
					ans.push_back(j);
					break;	
				}
				t--;
			}
		}
		x %= fac[i];
	}
	return ans;
}
posted @   cqbzjyh  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
主题色彩