【luogu 5367】康託展開 + 逆康託展開 + 樹狀數組

康託展開:求一個 1 ~ n 的排列的排名,如 1....n 的排名爲1

A[0] * (n-1)! + A[1] *(n-2) ! + ...+A[n-1] * 0!

A[i]表示小於當前位置的數的個數。

// 給定排列 求名次
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10 + 7;
typedef long long ll;
int a[maxn];
int cator(int *a)  // 計算排列 a 的名次
{
	ll ans = 0;
	for(int i = 1; i <= 5; i++) {
		ll cnt = 0, sum = 1, num = 1;
		for(int j = i + 1; j <= 5; j++) {
			if(a[i] > a[j]) cnt++;
			sum *= num;
			num++;
		}
		ans = (ans + cnt * sum);
	}
	cout << ans + 1 << endl;
	return 0;
}
int main()
{
	 int n;
	 for(int i = 1; i <= 5; i++) a[i] = i;
	 cator(a);
	return 0;
}

逆康託展開:已知一個排列的名次,求這個排列。

我們可以通過反解 A[0] * (n-1)! + A[1] *(n-2) ! + ...+A[n-1] * 0!  。很明顯是每一個階乘的係數代表小於當前這個數的個數就能確定當前數了。

// 逆康託展開
#include<bits/stdc++.h>
using namespace std;
int f[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; //階乘預處理
int reversecator(int n, int x) // 逆康託展開 n 個數的排列 名次爲 x
{
	vector<int>res; // 當前能選的數
	vector<int>ans; // 答案
	for(int i = 1; i <= n; i++) res.push_back(i);
	for(int i = n; i >= 1; i--) {
		int r = x % f[i-1];  // 餘數
		int c = x / f[i-1];  // 倍數 小於當前位置的數字有幾個
		x = r;
		sort(res.begin(), res.end());  // 對剩下的數排序
		ans.push_back(res[c]);  // 選第t+1個數
		res.erase(res.begin() + c); // 移除當前所選的數
	}
	for(auto it = ans.begin(); it != ans.end(); it++) cout << *it << " ";
	cout << endl;
	return 0;
}
int main()
{
	int n, x;
	while(scanf("%d %d", &n, &x) == 2) {
		reversecator(n, x - 1);
	}
	return 0;
}

康託展開+樹狀數組:我們在求康託展開時,爲了找到小於當前數的個數,我們開始使用\theta(n)的時間複雜度。但是我們可以使用樹狀數組把找小於當前數的個數優化的\theta(log(n))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000000 + 7;
const ll mod = 998244353;
int n, a[maxn];
ll tree[maxn];
ll f[maxn];
void init()  // 階乘預處理
{
	f[1] = 1;
	for(int i = 2; i <= maxn; i++) f[i] = (f[i-1] * i) % mod;
}
int lowbit(int x) 
{
	return x & (-x);
}
int add(int x)  // 更新
{
	while(x <= n) {
		tree[x] += 1;
		x += lowbit(x);
	}
	return 0;
}
int query(int x)  // 求和
{
	int res = 0;
	while(x > 0) {
		res += tree[x];
		x -= lowbit(x);
	}
	return res;
}
ll cantor(int* a)  // 康託展開
{
	memset(tree, 0, sizeof(tree));
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ll cnt = query(a[i]);
		add(a[i]);
		// a[i] 前面的個數是已經被使用過了的,還要去除本身, 所以a[i] - cnt -1
		ans = (ans % mod + (a[i] - cnt - 1) % mod * f[n-i] % mod) % mod;
	}
	return ans;
}
int main()
{
	init();
	while(scanf("%d", &n) == 1) {
		for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
		ll ans = cantor(a);
		printf("%lld\n", ans + 1);
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章