康托展開 逆康托展開

2021-10-10 13:00:54 字數 4188 閱讀 9304

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個數字,樸素思想就是用二分+樹狀陣列實現。但是利用

#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()

p5367 【模板】康托展開

題意:求 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題解:康托展開模板

**:

#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()

cf1443e e. long permutation

題意:給定乙個序列[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...