康托展开(排列组合)

一.康托展开

用处:首先康托展开是求1~n中任意排列得排名

(把 1~n 的所有排列按字典序排序,这个排列的位次就是它的排名)

康托展开可以在O(n^{2})  的复杂度内求出一个排列的排名,在用到树状数组优化时可以做到 

O( n log(n) )

因为排列是按字典序排名的,因此越靠前的数字优先级越高。也就是说如果两个排列的某一位之前的数字都相同,那么如果这一位如果不相同,就按这一位排序。

X = a[n] * (n-1)! + a[n-1] * (n-2)! + …… + a[i] * (i-1)! + …… + a[2] * 1! + a[1] * 0!

  式中,a[i]表示原数第i位之前有几个比他小得数没有出现,并且有0 <= a[i] < i(1 <= i <= n)

注意我们统计的是排名,因此最前面要 +1。;

所以最终的排列为X+1

康托展开(n^{2}

int a[N];
int n, m;
int jc[N];
void init() {
	jc[1] = 1;
	for (int i = 2; i <= 1e6; i++) {
		jc[i] = jc[i - 1] * i % mod;
	}
}
void contor() {
	int sum = 1;
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	for (int i = 1; i <= n; i++) {
		int cnt = 0;//计数
		for (int j = i + 1; j <= n; j++) {
			if (a[i] > a[j]) cnt++;//后面比a[i]小的有多少
		}
		sum = (sum + (cnt * jc[n - i])) ;
	}
	cout << sum << endl;
}

树状数组优化的康托展开(nlogn)

#include<cstdio>
#include<iostream>
using namespace std;
#define int long long
#define lowbit(x) x&-x
#define endl "\n"
const int N = 1e6 + 7;
const int mod = 998244353;
int a[N];
int n, m;
int jc[N];
int tr[N];
void init() {
	jc[1] = 1;
	for (int i = 2; i <= 1e6; i++) {
		jc[i] = jc[i - 1] * i % mod;
	}
}
void add(int x) {
	for (int i = x; i <= n; i += lowbit(i)) {
		tr[i] += 1;
	}
}
int query(int x) {
	int res = 0;
	for (int i = x; i > 0; i -= lowbit(i)) {
		res += tr[i];
	}
	return res;
}
void contor() {
	int sum = 1;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		sum = (sum + ((a[i] - 1) - query(a[i] - 1)) * jc[n - i] % mod)  % mod;
		add(a[i]);
	}
	//cout << query(4);
	cout << sum << endl;
}

signed main() {
	init();
	scanf("%d", &n);
	contor();
	return 0;
}

二.逆康托展开

因为排列的排名和排列是一一对应的,所以康托展开满足双射关系,是可逆的。可以通过类似上面的过程倒推回来。X需要减1

逆康托的求解排列流程:

求解第n位的数字:X/(n-1)!= a 余数是 c ,也就是找a数列中的a+1小的数

求解第i位的数字:    c/(i-1)!=  b  余数是c,,也就是找a数列中的b+1小的数,把已找到的数排除

void reverse_contor() {
	int k;
	cin >> k;//排列第几
	k--;
	vector<int> rest;  // 存放当前可选数,有序
	for (int i = 1; i <= n; i++)
		rest.push_back(i);
	for (int i = n; i >= 1; i--) {
		int r = k % jc[i - 1];
		int t = k / jc[i - 1];
		k = r;
		cout << rest[t] << " ";//每次找到就输出
		rest.erase(rest.begin() + t);       // 移除选做当前位的数
	}
	cout << endl;
}

最后附上多次查询的代码

#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
#define int long long
#define lowbit(x) x&-x
#define endl "\n"
const int N = 1e5 + 10;
const int mod = 998244353;
int n, m;
int jc[N];
//计算阶乘
void init() {
	jc[1] = 1;
	jc[0] = 1;
	for (int i = 2; i <= 20; i++) {
		jc[i] = jc[i - 1] * i ;
	}
}
int contor(int x[]) {
	int sum = 1;
	for (int i = 1; i <= n; i++) {
		int cnt = 0;//计数
		for (int j = i + 1; j <= n; j++) {
			if (x[i] > x[j]) cnt++;//后面比a[i]小的有多少
		}
		sum +=  (cnt * jc[n - i]);
	}
//	cout << sum << endl;
	return sum ;
}
int val[25];
void reverse_contor() {
	int k;
	cin >> k;
	k--;
	vector<int> rest;  // 存放当前可选数,有序
	for (int i = 1; i <= n; i++)
		rest.push_back(i);
	for (int i = n; i >= 1; i--) {
		int r = k % jc[i - 1];
		int t = k / jc[i - 1];
		k = r;
		cout << rest[t] << " ";
		rest.erase(rest.begin() + t);       // 移除选做当前位的数
	}
	cout << endl;
}
signed  main() {
	init();
	scanf("%lld%lld", &n, &m);
	while (m--) {
		string s;
		cin >> s;
		if (s[0] == 'P') {
			reverse_contor();
		} else if (s[0] == 'Q') {
			for (int i = 1; i <= n; i++) scanf("%lld", &val[i]);
			printf("%lld\n", contor(val));
		}
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Smile灬凉城666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值