2.模板
3.典型例題
康托展開可以求乙個序列是第幾個排列,即求得[2, 1, 3]是第3個排列
逆康托展開可以求得第k個排列是多少,即求得第3個排列為[2, 1, 3]
基於這個性質,可以使用康托展開把乙個序列做雜湊,對映為乙個數字。
康托展開公式:當前排列的的排名為: ran
k=an
∗(n−
1)!+
an−1
∗(n−
2)!+
...+
a1∗0
+1
rank = a_n*(n - 1)! + a_*(n - 2)! + ... + a_1 * 0 + 1
rank=a
n∗(
n−1)
!+an
−1∗
(n−2
)!+.
..+a
1∗0
+1其中,其中a[i]表示第i個數在未出現的數中排第幾。
樸素的想法為當遍歷到當前數字a[i]時,向前掃瞄,看有多少個還沒有用過的數字。這樣為o(n
2)
o(n^2)
o(n2)
但是可以記使用過為0,沒有使用過為1,這樣可以使用樹狀陣列維護字首和,即可知道當前數字在沒有用過的數字中排第幾。
逆康托展開是康托展開的逆過程,倒著從(n-1)!的階乘開始往1!做除法,然後按照康托展開的邏輯求即可。
以[4,2,5,1,3]為例:
82/(4!),商為3,餘數為10:那麼要找沒有出現過的第3+1=4個數字,即4.
10/(3!),商為1,餘數為4:那麼要找沒有出現過的第1+1=2個數字,即2.
4/(2!),商為2,餘數為0:那麼要找沒有出現過的第2+1=3個數字,即5.
0/(1!),商為0,餘數為0:那麼要找沒有出現過的第0+1=1個數字,即1.
最後將未填充的數3填到最後一位
基於上述思想可以發現核心問題就找沒有出現過的第k個數字,樸素思想就是用二分+樹狀陣列實現。但是利用
p5367 【模板】康托展開#include
using
namespace std;
intconst n =
2e6+
10, mod =
998244353
;typedef
long
long ll;
ll fact[n]
;int c[n]
, n, m, a[n]
;// n為序列長度,m為大於n的第乙個2的冪次方,注意這裡c陣列空間要開雙倍
intlowbit
(int x)
// 單點修改
void
add(
int x,
int y)
// 找到x屬於還沒有用過中的第幾個
intrnk
(int x)
// 找到還沒有用過中的數字的第k大
intkth
(int k)
return res +1;
// 因為返回的是前乙個位置
}// 康托展開: [1, 4, 2, 3] -> 2
intencode
(int a)
return
(res +1)
% mod;
}// 逆康托展開: 2 -> [1, 4, 2, 3]
vector<
int>
decode
(ll x)
return res;
}int
main()
題意:求 1∼n
1\sim n
1∼n 的乙個給定全排列在所有 1∼n
1\sim n
1∼n 全排列中的排名。結果對 998244353 取模。 1
<=n
<=1
06
1 <= n <= 10^6
1<=n
<=1
06題解:康托展開模板
**:
cf1443e e. long permutation#include
using
namespace std;
intconst n =
2e6+
10, mod =
998244353
;typedef
long
long ll;
typedef pair<
int,
int> pii;
ll fact[n]
;int c[n]
, n, a[n]
;// n為序列長度,m為大於n的第乙個2的冪次方
intlowbit
(int x)
// 單點修改
void
add(
int x,
int y)
// 找到x屬於還沒有用過中的第幾個
intrnk
(int x)
// 康托展開: [1, 4, 2, 3] -> 2
intencode
(int a)
return
(res +1)
% mod;
}int
main()
題意:給定乙個序列[1, 2, 3, …, n], 序列長度為1e5。有1e5個操作,分別為:操作1:1 l r,即計算序列區間[l, r]的區間和;操作2 :2 k,找出當前序列往後字典序第k大。1 <= k <= n。
題解:由於1 <= k <= n,因此就算執行1e5次操作2,那麼也只會是找序列的1e10個。而14! > 1e10,因此無論執行多少次操作,本質上只會改變序列的最後14位數字。所以,對於字首和,可以直接暴力維護,然後對於每次操作2,求出最後14位數字的序列,然後暴力修改字首和陣列sum即可。
這裡有乙個trick,求[1, 2, 3, …, n]的後面第k個序列可以直接康托展開求解,但是如果求[2, 3, 1, …, n , n - 1]這樣的序列後面的第k個是無法直接逆康托展開的。然而本題的序列是從[1, 2, 3, …, n]開始的,我們可以維護乙個x,記錄一下當前操作2需要求解的序列是相對於[1, 2, 3,…,n]的第幾個,即每次都是x = x + k。然後求解最後14個數字的逆康托展開,可以先求出[1, 2, …, 14]往後的第k個,然後加上偏移量offset = n - 14。
**:
#include
using
namespace std;
intconst n =
2e5+10;
typedef
long
long ll;
typedef pair<
int,
int> pii;
int n, m, t, q;
int a[n]
, c[
100]
;ll sum[n]
, fact[
100]
;int
lowbit
(int x)
// 單點修改
void
add(
int x,
int y)
intkth
(int k)
return res +1;
// 因為返回的是前乙個位置
}// 逆康托展開: 2 -> [1, 4, 2, 3]
vector<
int>
decode
(ll x)
return res;
}int
main()
int offset =0;
// 計算偏移量
if(n >
14) offset = n -14;
ll k =0;
for(
int i =
1, op, l, r; i <= q;
++i)
else}}
return0;
}
康托展開 康托逆展開
x a n n 1 a n 1 n 2 a i i 1 a 1 0 其中a i 為當前未出現的元素中是排在第幾個 從0開始 這就是康托展開。康托展開可用 實現。編輯 把乙個整數x展開成如下形式 x a n n 1 a n 1 n 2 a i i 1 a 2 1 a 1 0 其中a i 為當前未出現的...
康托展開 逆康托展開
康托展開 問題 給定的全排列,計算出它是第幾個排列 求序列號 方法 康托展開 對於乙個長度為 n 的排列 num 1 n 其序列號 x 為 x a 1 n i a 2 n 2 a i n i a n 1 1 a n 0 其中a i 表示在num i 1 n 中比num i 小的數的數量 includ...
康托展開 逆康托展開
用途 康托展開是一種雙射,用於排列和整數之間的對映,可用於排列的雜湊 康托展開 公式 i n1pi i 1 sum limits p i i 1 i n 1 pi i 1 其中p ip i pi 為第i ii個數構成的逆序的個數,n為排列數的個數 例 排列 2134 i n1pi i 1 sum l...