一.引出康托展開
動態規劃題有一類分支叫狀壓dp,意思就是把狀態壓縮為乙個二進位制陣列,然後轉為十進位制數儲存。一般n的大小不會超過20,因為20個狀態的組合就有2^20,也就是1e6種可能。
對於一些題目,緊緊利用狀態壓縮,會發現狀態的組合數遠遠超過1e6的範圍,那時候我們沒有辦法在1s內遍歷出來,或者大到根本連陣列都開不出來的時候,一般情況下就需要用到康托展開
例如:對於乙個組合 1 2 3 4 5 6 7 8,a操作可以讓其轉變為8 7 6 5 4 3 2 1,b操作可以讓其轉變為4 1 2 3 6 7 8 5,c操作可以讓其轉變為1 7 2 4 5 3 6 8
給出乙個初始組合和目標組合,問由初始到目標最少的變換步驟,若多種則選字典樹最小的那種?
對於這種題,如果我們把1~8看作0~7,拿這8個數的當作乙個狀態來儲存,需76543210種狀態(且裡邊有些狀態根本就不可能出現,如11111111),這樣肯定是不可行的。
如果我們利用狀態壓縮把它轉為2進製,0為000,1為001,2為010….7為111,那麼8個數連在一起共有24位,也就是需要2^24 = 16777216個狀態進行儲存,然後縮小了7倍,但是陣列依舊太大了
這時候,我們需要考慮康托展開對狀態進行定義
二.關於康托展開
和狀壓陣列不同,康托展開陣列a[i]代表的是該序列從第i位開始到最後一位,第i位的數排第幾(排名和i都是從0開始)
舉個例子:3,5,4,1,2中:a[0] = 2,a[1] = 3, a[2] = 2,a[3] = 0, a[4] = 0
那麼3 5 4 1 2的狀態值 = a[0]✖️4! + a[1]✖️3! + a[2]✖️2! + a[3]✖️1! + a[4]✖️0! = 70
也就是說,康托展開能夠把狀態壓縮到極致(即像上邊那種沒有用過的諸如11111111等都被拋棄掉,只剩有用的狀態存在),即節省了空間也節省了時間。
三.關於康托逆展開
我們在二中得到的70可以通過康托逆展開重新得到3,5,4,1,2,方法如下:
70 / 4! = 2餘22,因此a[0] = 2;
22 / 3! = 3餘4,因此a[1] = 3;
4 / 2! = 2餘0,因此a[2] = 2;
0 / 1! = 0餘0,因此a[3] = 0;
0 / 0! = 0餘0,因此a[4] = 0;
在1, 2 , 3, 4, 5中,第2大(從0開始算)的數是3
在1, 2 , 4, 5中,第3大(從0開始算)的數是5
在1, 2 , 4中,第2大(從0開始算)的數是4
在1, 2中,第0大(從0開始算)的數是1,最後乙個數就是2
因此就能得到序列3,5,4,1,2
以上就是康托逆展開
四.**實現:
(1)康托展開實現**:
fact[10]; //fact[i]儲存i的階乘的值
//把陣列s合併為乙個狀態num, k代表陣列長度
void cantor (int s, ll &num, int k)
num += fact[k - i - 1] * cnt;
}}
(2)康托逆展開**:
fact[10]; //fact[i]儲存i的階乘的值
//把狀態值num轉回陣列s
bool book[10]; //判斷序列中下角標為i的數是否已經標記
void inv_cantor (int s, ll num, int k) }}
}
理論就是這些~
如果有寫的不對或者不全面的地方 可通過主頁的****進行指正,謝謝
數學 數論 康托展開與逆康托展開
康托展開 可以理解為把乙個全排列對映到乙個數上面,因為全排列如果按照從小到大或者從大到小,肯定是有乙個確定的序列的。一般是從小到大的序列個數。我們就是要求出這個序列的位置。想法很簡答,就是求出前面比他小的個數就可以了。理解為乙個每位都是階乘進製的數轉化為10進製的數。思路如下 先準備求每一位的階乘,...
數學 數論 康托展開與逆康托展開
康托展開 可以理解為把乙個全排列對映到乙個數上面,因為全排列如果按照從小到大或者從大到小,肯定是有乙個確定的序列的。一般是從小到大的序列個數。我們就是要求出這個序列的位置。想法很簡答,就是求出前面比他小的個數就可以了。理解為乙個每位都是階乘進製的數轉化為10進製的數。思路如下 先準備求每一位的階乘,...
康托展開 康托逆展開
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 為當前未出現的...