1. 程式人生 > 其它 >數學/數論專題-學習筆記:康託展開

數學/數論專題-學習筆記:康託展開

目錄

1.概述

康託展開,是全排列問題中常用的一種演算法。

康託展開:已知一個 \(n\) 階全排列 \(a\),求出這是第幾個全排列(按照字典序排序)。

2.實現

例題

康託展開的公式:\(a_1 \times (n - 1)! + a_2 \times (n - 2)! +...+ a_{n - 1} \times 1! + a_n \times 0! + 1\)。本部落格中約定:\(0! = 0\)

上式中,\(a_i\) 表示比 \(a_i\) 小並且不在 \(i\) 位置前面的數的個數。

那麼我們以 5 2 3 1 4 為例,模擬康託展開的步驟。

  1. 5 小的數有四個,其中有 0
    個在前面,因此 \(a_1 = 4\)。這 4 個數能夠產生 \(4 \times 4!\) 個全排列。
  2. 2 小的數有一個,其中有 0 個在前面,因此 \(a_2 = 1\)。考慮到第 1 個數已經確定,那麼剩下這個數能夠產生 \(1 \times 3!\) 個全排列。
  3. (此處省略若干字)
  4. 最後的結果為 \(4 \times 4! + 1 \times 3! + 1 \times 2! + 0 \times 1! + 0 \times 0! + 1 = 105\)

為什麼這麼做?

比如第一位,我們可以發現:對於 1 2 3 4 這四個數而言,我們需要計算 5 不在第一位的全排列。根據排列組合的知識,結果為 \(4 \times 4 \times 3 \times 2 \times 1 = 4 \times 4!\)

。其餘同理。

那麼程式碼呢?其實還是比較好寫的。

計算 \(a_i\) 我們可以使用權值線段樹、set 等來解決。

當然我用的是 FHQ Treap,結果被卡常了。

用 FHQ Treap 的原因是我最近在學 FHQ Treap 所以用這個,其實權值線段樹或 set 或樹狀陣列會更簡潔。

程式碼:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int MAXN = 1e6 + 10, P = 998244353;
int n, a[MAXN], cnt, root;
LL ans, f[MAXN];

struct node
{
	int l, r, size, val, key;
}tree[MAXN];

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}

int Make_Node(int val)
{
	int now = ++cnt;
	tree[now].size = 1;
	tree[now].val = val;
	return now;
}

void update(int x) {tree[x].size = tree[tree[x].l].size + tree[tree[x].r].size + 1;}

void split(int now, int val, int &x, int &y)
{
	if (now == 0) x = y = 0;
	else
	{
		if (tree[now].val <= val)
		{
			x = now;
			split(tree[now].r, val, tree[now].r, y);
		}
		else
		{
			y = now;
			split(tree[now].l, val, x, tree[now].l);
		}
		update(now);
	}
}

int merge(int x, int y)
{
	if (!x || !y) return x + y;
	if (rand() & 1)
	{
		tree[x].r = merge(tree[x].r, y);
		update(x); return x;
	}
	else
	{
		tree[y].l = merge(x, tree[y].l);
		update(y); return y;
	}
}

void Insert(int val)
{
	int x, y; split(root, val - 1, x, y);
	root = merge(merge(x, Make_Node(val)), y);
}

int Find(int val)
{
	int x, y, ans;
	split(root, val - 1, x, y);
	ans = tree[x].size;
	root = merge(x, y);
	return ans;
}

int main()
{
	srand(time(0));
	n = read(); f[0] = 1; cnt = root = 0;
	for (int i = 1; i <= n; ++i) a[i] = read();
	for (int i = 1; i <= n; ++i) f[i] = f[i - 1] * i % P;
	f[0] = 0;
	for (int i = 1; i <= n; ++i)
	{
		Insert(a[i]); ans = (ans + ((LL)a[i] - 1 - Find(a[i])) * f[n - i]) % P;
//		printf("%d/%d/%d\n", f[n - i], Find(a[i]), ans);//除錯用
	}
	printf("%lld\n", ans + 1);
	return 0;
}