數學/數論專題-學習筆記:康託展開
阿新 • • 發佈:2022-04-14
目錄
。其餘同理。
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
為例,模擬康託展開的步驟。
- 比
5
小的數有四個,其中有0
4
個數能夠產生 \(4 \times 4!\) 個全排列。 - 比
2
小的數有一個,其中有0
個在前面,因此 \(a_2 = 1\)。考慮到第1
個數已經確定,那麼剩下這個數能夠產生 \(1 \times 3!\) 個全排列。 - (此處省略若干字)
- 最後的結果為 \(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; }