数论--康托展开

Posted 东流vip

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数论--康托展开相关的知识,希望对你有一定的参考价值。

先声明,是康托,不是康娜,hhh

《小林家的龙女仆》

 

在我们做题中,搜索也好,动态规划也好,我们往往有时候需要用一个数字表示一种状态

 

比如有8个灯泡排成一排,如果你用0和1表示灯泡的发光情况

那么一排灯泡就可以转换为一个二进制数字了

 

比如

01100110 = 102

11110000 = 240

10101010 = 170

 

通过这些十进制数,只要把他们展开,我们就知道灯泡的状态了

 

步入正题:

康托展开就是用一个数代表一串数,把这个数展开为这串数

而康托逆展开就是把一串数转换为它对应的一个数

比如:

123  ->  0

132  ->  1

213  ->  2

231  ->  3

312  ->  4

321  ->  5

从左到右就是康托逆展开,从右到左就是康托展开

那么,应该如何做到呢??

例如序列:1,2,3,4。改变次序后为3,4,2,1(也可以是其他的序列),康托展开告诉我们每种序列都对应着独一无二的一个数字。

引入一个数组,a[m]: 其中m代表着该序列的第m位(也就是序列下标啦),a[m]则代表从第m位到最后一位,第m位上的数是排第几小的(从1开始,1排第0...)

比如3,4,2,1: a[0]=2(a[o]代表3在3,4,2,1中排第几小),a[1]=2(a[1]代表4在4,2,1中排第几小),a[2]=1(a[1]代表2在2,1中排第几小),a[3]=1(a[1]代表1在1中排第几小)

此时X(3,4,2,1)=a[0]*(4-1)!+a[1]*(4-2)!+a[2]*(4-3)!+a3*(4-4)!=17      (这一步能理解吗?全排列问题)

显然X(1,2,3,4)=0

 

那么反向思考一下:如果已知序列号,如何求出对应的序列?

a[0]=17/3!=2......5

a[1]=5/2!=2......1

a[2]=1/1!=1......0

a[3]=0/0!=0

1,2,3,4中排第2的是3

1,2,4中排第2的是4

1,2中排第1的是2

1中排第0的是1

所以序列号17对应的序列就是3,4,2,1.

贴上代码:

 1 #include<cstdio>
 2 #include<cstring> 
 3 typedef long long LL;
 4 
 5 LL fac[1010];
 6 
 7 
 8 void cantor(int s[], LL num, int k){//康托展开,把一个数字num展开成一个数组s,k是数组长度
 9     int t;
10     bool h[k];//0到k-1,表示是否出现过 
11     memset(h, 0, sizeof(h)); 
12     for(int i = 0; i < k; i ++){
13         t = num / fac[k-i-1];
14         num = num % fac[k-i-1];
15         for(int j = 0, pos = 0; ; j ++, pos ++){
16             if(h[pos]) j --;
17             if(j == t){
18                 h[pos] = true;
19                 s[i] = pos + 1;
20                 break;
21             }
22         }
23     }
24 }
25 void inv_cantor(int s[], LL &num, int k){//康托逆展开,把一个数组s换算成一个数字num 
26     int cnt;
27     num = 0;
28     for(int i = 0; i < k; i ++){
29         cnt = 0;
30         for(int j = i + 1; j < k; j ++){
31             if(s[i] > s[j]) cnt ++;//判断几个数小于它
32         }
33         num += fac[k-i-1] * cnt;
34     }
35 }
36 
37 int main()
38 {
39     fac[0] = 1;
40     for(int i = 1; i <20 ; i ++) 
41         fac[i] = fac[i-1] * i;
42     int a[20];
43     a[0]=3;//数组从0开始,元素值从1开始 
44     a[1]=4;
45     a[2]=2;
46     a[3]=1;
47     LL ans;
48     inv_cantor(a,ans,4);
49     printf("%lld\\n",ans);
50     int b[20];
51     cantor(b,4,17);
52     for(int i=0;i<4;i++)
53     printf("%d ",b[i]); 
54 }

 

康托展开经典题:hdu 1430

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

 直接上代码,解释在注释中:

  1 #include<cstdio>
  2 #include<cstring> 
  3 #include<string>
  4 #include<queue>
  5 #include<iostream>
  6 #include<algorithm>
  7 using namespace std;
  8 
  9 typedef long long LL;
 10 
 11 LL fac[1010];
 12 int vis[50000];
 13 string ans[50000];//记录下到下标最近的走法 
 14 
 15 struct node
 16 {
 17     string ans;
 18     int s[10];
 19     LL status;
 20  };
 21  
 22 void cantor(int s[], LL num, int k){//康托展开,把一个数字num展开成一个数组s,k是数组长度
 23     int t;
 24     bool h[k];//0到k-1,表示是否出现过 
 25     memset(h, 0, sizeof(h));
 26     for(int i = 0; i < k; i ++){
 27         t = num / fac[k-i-1];
 28         num = num % fac[k-i-1];
 29         for(int j = 0, pos = 0; ; j ++, pos ++){
 30             if(h[pos]) j --;
 31             if(j == t){
 32                 h[pos] = true;
 33                 s[i] = pos + 1;
 34                 break;
 35             }
 36         }
 37     }
 38 }
 39 void inv_cantor(int s[], LL &num, int k){//康托逆展开,把一个数组s换算成一个数字num 
 40     int cnt;
 41     num = 0;
 42     for(int i = 0; i < k; i ++){
 43         cnt = 0;
 44         for(int j = i + 1; j < k; j ++){
 45             if(s[i] > s[j]) cnt ++;//判断几个数小于它
 46         }
 47         num += fac[k-i-1] * cnt;
 48     }
 49 }
 50 
 51 void bfs()
 52 {
 53     memset(vis,0,sizeof(vis));
 54     queue<node>q;
 55     node f,next;
 56     for(int i=0;i<8;i++)
 57     f.s[i]=i+1;//最初状态
 58     f.ans="";
 59     inv_cantor(f.s,f.status,8);
 60     vis[f.status]=1;
 61     q.push(f);
 62     while(!q.empty())
 63     {
 64         f=q.front();
 65         q.pop();
 66         next=f;//第一种变换 
 67         swap(next.s[0],next.s[7]);
 68         swap(next.s[1],next.s[6]);  
 69         swap(next.s[2],next.s[5]);  
 70         swap(next.s[3],next.s[4]);    
 71         inv_cantor(next.s,next.status,8);// 康托逆展开,将next.s序列对应的序列号存在status里面 
 72         if(vis[next.status]==0)//若未访问 
 73         {
 74             next.ans+=\'A\';//进行了一次A变换 
 75             vis[next.status]=1; //标记为访问 
 76             q.push(next);
 77             ans[next.status]=next.ans;//序列号为next.status对应的答案 
 78         }    
 79         
 80         next=f;//第二种变换 
 81         swap(next.s[3],next.s[2]); 
 82         swap(next.s[2],next.s[1]); 
 83         swap(next.s[1],next.s[0]);  
 84         swap(next.s[4],next.s[5]); 
 85         swap(next.s[5],next.s[6]); 
 86         swap(next.s[6],next.s[7]);  
 87         inv_cantor(next.s,next.status,8);
 88         if (vis[next.status]==0)  
 89         {  
 90             next.ans+=\'B\';  
 91             vis[next.status]=1;  
 92             q.push(next);  
 93             ans[next.status]=next.ans;  
 94         } 
 95         
 96         next=f; //第三种变换 
 97         swap(next.s[1],next.s[6]);
 98         swap(next.s[6],next.s[5]); 
 99         swap(next.s[5],next.s[2]);  
100         inv_cantor(next.s,next.status,8);  
101         if (vis[next.status]==0)  
102         {  
103             next.ans+=\'C\';  
104             vis[next.status]=1;  
105             q.push(next);  
106             ans[next.status]=next.ans;  
107         
108         }     
109     }
110 }
111     
112 int main()
113 {
114     fac[0] = 1;
115     for(int i = 1; i <20 ; i ++) 
116         fac[i] = fac[i-1] * i;
117     bfs();
118     
119     string as,bs;
120     while(cin>>as>>bs)
121     {
122         int a[10];
123         for(int i=0;i<8;i++)
124         a[i]=as[i]-\'0\';
125         int b[10];
126         for(int i=0;i<8;i++)
127         b[i]=bs[i]-\'0\';
128         for(int i=0;i<8;i++)//把a数组看为{1,2,3,4,5,6,7,8}b数组跟着变化 
129         {
130             for(int j=0;j<8;j++)
131             {
132                 if(b[i]==a[j])
133                 {
134                     b[i]=j+1;
135                     break;
136                 }
137             }
138         }
139         LL res;
140         inv_cantor(b,res,8);//b数组的序列号为res 
141         cout<<ans[res]<<endl; //直接输出序列号为res对应的答案即可 
142     }
143 
144  } 

注意,用c++提交错误的话请换g++提交

以上是关于数论--康托展开的主要内容,如果未能解决你的问题,请参考以下文章

康拓展开

康托展开和逆康托展开

宽搜经典题之二——8数码难题+康托展开

全排列的编码与解码——康托展开 (附完整代码)

康托展开 / 逆康托展开

康托展开 & 逆康托展开