ACM數論之旅12---康託展開((*゚▽゚*)裝甲展開,主推動器啓動,倒計時3,2,1......)

 

在咱們作題中,搜索也好,動態規劃也好,咱們每每有時候須要用一個數字表示一種狀態php

 

好比有8個燈泡排成一排,若是你用0和1表示燈泡的發光狀況ios

那麼一排燈泡就能夠轉換爲一個二進制數字了c++

 

好比算法

01100110 = 102數組

11110000 = 240ide

10101010 = 170測試

 

經過這些十進制數,只要把他們展開,咱們就知道燈泡的狀態了spa

 

若是這題是一個動態規劃題code

而後咱們就拿這些數字作一些轉移了,blog

好比dp[102],dp[240],dp[170]等等

這對題目頗有幫助

 

 

 

 

 

 

 

 

上面講的那些就是所謂的狀態壓縮了,須知詳細的狀態壓縮能夠去百度

或者有機會我本身去寫一篇博客(這是flag(/TДT)/)

 

 

 

 

 

 

 

 

那對於有些題,咱們即便狀態壓縮後,數字太大,數組都開不下,麻煩的題目(/TДT)/

這些題目也要看狀況,好比我接下來要講的康託展開

 

 

 

 

 

 

 

康託展開經典題:hdu 1430

http://acm.hdu.edu.cn/showproblem.php?pid=1430

 

在魔方風靡全球以後不久,Rubik先生髮明瞭它的簡化版——魔板。魔板由8個一樣大小的方塊組成,每一個方塊顏色均不相同,可用數字1-8分別表示。任一時刻魔板的狀態可用方塊的顏色序列表示:從魔板的左上角開始,按順時針方向依次寫下各方塊的顏色代號,所獲得的數字序列便可表示此時魔板的狀態。例如,序列(1,2,3,4,5,6,7,8)表示魔板狀態爲:

1 2 3 4
8 7 6 5

對於魔板,可施加三種不一樣的操做,具體操做方法以下:

A: 上下兩行互換,如上圖可變換爲狀態87654321
B: 每行同時循環右移一格,如上圖可變換爲41236785
C: 中間4個方塊順時針旋轉一格,如上圖可變換爲17245368

給你魔板的初始狀態與目標狀態,請給出由初態到目態變換數最少的變換步驟,如有多種變換方案則取字典序最小的那種。

 

Input
每組測試數據包括兩行,分別表明魔板的初態與目態。
 
Output
對每組測試數據輸出知足題意的變換步驟。
 
Sample Input
12345678
17245368
12345678
82754631
 
Sample Output
C
AC

 

 

 

 

 

 

 

咱們看這題,總共有8個數字,1~8,假如咱們把他們當作0~7

那麼每一個數字能夠轉換爲一個3位二進制

0:000

1:001

2:010

3:011

4:100

5:101

6:110

7:111

 

而後12345678這個狀態咱們能夠表示爲二進制000001010011100101110111,總共3*8=24位,

2^24 = 16777216,數組根本開不下啊

 

這時,咱們發現了,有一些狀態,根本沒有用到,由於這題已經規定了有8個數字,每一個數字只出現一次

好比000000000000000000000000這個狀態,你說可能出現嗎?(o ° ω ° O )

 

 

這個時候,康託就對這種題目作了研究(o ° ω ° O )

 

這種每一個數字只出現一次的問題的因此狀況,總共才n!個狀況(這個問題叫作全排列)

 

康託的一套算法能夠正好產生n!個數字

好比:

123  ->  0

132  ->  1

213  ->  2

231  ->  3

312  ->  4

321  ->  5

 

 

 

 

這是如何作到的(/≥▽≤/)

在峯神的博客裏面有很好的解釋(對不起了峯神≖‿≖✧,拿過來抄一下)

 

 

康託展開1

 

 

康託展開2

 

 

(/≥▽≤/)好神奇

 

 

 

因而乎,康託展開模板:

 1 void cantor(int s[], LL num, int k){//康託展開,把一個數字num展開成一個數組s,k是數組長度
 2     int t;
 3     bool h[k];//0到k-1,表示是否出現過 
 4     memset(h, 0, sizeof(h)); 
 5     for(int i = 0; i < k; i ++){
 6         t = num / fac[k-i-1];
 7         num = num % fac[k-i-1];
 8         for(int j = 0, pos = 0; ; j ++, pos ++){
 9             if(h[pos]) j --;
10             if(j == t){
11                 h[pos] = true;
12                 s[i] = pos + 1;
13                 break;
14             }
15         }
16     }
17 }
18 void inv_cantor(int s[], LL &num, int k){//康託逆展開,把一個數組s換算成一個數字num 
19     int cnt;
20     num = 0;
21     for(int i = 0; i < k; i ++){
22         cnt = 0;
23         for(int j = i + 1; j < k; j ++){
24             if(s[i] > s[j]) cnt ++;//判斷幾個數小於它
25         }
26         num += fac[k-i-1] * cnt;
27     }
28 }

 

 

 

 

 

 

 

付上AC代碼:

(這代碼我在杭電上用c++交居然CE了,g++就沒問題,CE的內容是個人那個模板,說什麼不能bool h[k]這樣聲明類型,c++當心眼,這有什麼關係嘛(´・ω・)ノ,我還只是個孩子)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<string>
 5 #include<algorithm>
 6 #include<queue>
 7 using namespace std; 
 8 typedef long long LL;
 9 const int N = 8;
10 queue <LL> que;
11 string ans[50000];
12 char str1[10], str2[10];
13 bool vis[50000];
14 
15 int map[10];//映射
16 int num[10];
17  
18 LL fac[N];//階乘
19 void change(int s[], int o){//o分別是0,1,2,表示ABC三種變化
20     switch(o){
21         case 0:
22             for(int i = 0; i < 4; i ++) swap(s[i], s[8-i-1]);
23             break;
24         case 1:
25             for(int i = 3; i >= 1; i --) swap(s[i], s[i-1]);
26             for(int i = 4; i < 7; i ++) swap(s[i], s[i+1]);
27             break;
28         case 2:
29             swap(s[1], s[6]);
30             swap(s[6], s[5]);
31             swap(s[5], s[2]);    
32             break;
33     } 
34 } 
35 void cantor(int s[], LL num, int k){//康託展開,把一個數字num展開成一個數組s,k是數組長度
36     int t;
37     bool h[k];//0到k-1,表示是否出現過 
38     memset(h, 0, sizeof(h)); 
39     for(int i = 0; i < k; i ++){
40         t = num / fac[k-i-1];
41         num = num % fac[k-i-1];
42         for(int j = 0, pos = 0; ; j ++, pos ++){
43             if(h[pos]) j --;
44             if(j == t){
45                 h[pos] = true;
46                 s[i] = pos + 1;
47                 break;
48             }
49         }
50     }
51 }
52 void inv_cantor(int s[], LL &num, int k){//康託逆展開,把一個數組s換算成一個數字num 
53     int cnt;
54     num = 0;
55     for(int i = 0; i < k; i ++){
56         cnt = 0;
57         for(int j = i + 1; j < k; j ++){
58             if(s[i] > s[j]) cnt ++;//判斷幾個數小於它
59         }
60         num += fac[k-i-1] * cnt;
61     }
62 }
63 void init(){
64     fac[0] = 1;
65     for(int i = 1; i < N; i ++) fac[i] = fac[i-1] * i;
66     int a[8], b[8];
67     LL temp, temp2;
68     que.push(0);
69     vis[0] = true;
70     while(!que.empty()){
71         LL temp = que.front(); que.pop();
72         cantor(a, temp, 8);
73         for(int i = 0; i < 3; i ++){
74             copy(a, a+8, b);
75             change(b, i);
76             inv_cantor(b, temp2, 8);
77             if(!vis[temp2]){
78                 que.push(temp2);
79                 vis[temp2] = true;
80                 ans[temp2] = ans[temp] + (char)('A' + i);
81             }
82         }
83     }
84 }
85 int main(){
86     init();
87     while(~scanf("%s", str1)){
88         scanf("%s", str2);
89         //先把全部初始狀態都轉換成12345678
90         //最終狀態根據初始狀態的轉換而轉換 
91         //這樣只要一次預處理就能夠解決問題了 
92         for(int i = 0; i < 8; i ++) map[str1[i] - '0'] = i + 1;
93         for(int i = 0; i < 8; i ++) num[i] = map[str2[i] - '0'];
94         LL temp;
95         inv_cantor(num, temp, 8);
96         cout << ans[temp] << endl;
97     }
98 }
View Code

 

 

 

 

 

 

 

 

宇宙我來啦~\(≧▽≦)/~

相關文章
相關標籤/搜索