博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ACM数论之旅12---康托展开((*゚▽゚*)装甲展开,主推进器启动,倒计时3,2,1......)...
阅读量:4546 次
发布时间:2019-06-08

本文共 4523 字,大约阅读时间需要 15 分钟。

 

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

 

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

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

 

比如

01100110 = 102

11110000 = 240

10101010 = 170

 

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

 

如果这题是一个动态规划题

然后我们就拿这些数字做一些转移了,

比如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
2 #include
3 #include
4 #include
5 #include
6 #include
7 using namespace std; 8 typedef long long LL; 9 const int N = 8;10 queue
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 //先把所有初始状态都转换成1234567890 //最终状态根据初始状态的转换而转换 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

 

 

 

 

 

 

 

 

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

 

转载于:https://www.cnblogs.com/linyujun/p/5205760.html

你可能感兴趣的文章
力扣——第N个泰波那契数
查看>>
服务器 以及HTTP请求的关系
查看>>
JMETER使用
查看>>
如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案!(zz)...
查看>>
整体性学习的一般顺序 如何进行整体性学习
查看>>
罗永浩简历(自荐新东方的简历)
查看>>
js特效,轻松实现内容的无缝平滑滚动
查看>>
[leetcode]Valid Palindrome
查看>>
LeetCode第四题,Add Two Numbers
查看>>
常见的JavaScript面试题
查看>>
mysql删除重复数据
查看>>
[DataStructure]多项式加法与乘法--A.数组存储(适用于零元系数少的多项式)
查看>>
大批量数据处理
查看>>
JavaScript笔记基础篇(三)
查看>>
第一次作业
查看>>
lwip 分析一
查看>>
写出高效优美的单片机C语言代码
查看>>
我的单元测试
查看>>
jQuery.Validate常用的一些规则
查看>>
Java 编码规范
查看>>