zoukankan      html  css  js  c++  java
  • ACM数论之旅12---康托展开((*゚▽゚*)装甲展开,主推进器启动,倒计时3,2,1......)

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

    比如有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<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

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

  • 相关阅读:
    mysql5.7版本centos8环境修改my.cnf配置文件
    mysql5.7使用r2dbc持久层框架性能飙升,一小时插入623万数据,平均每秒插入1723条
    mysql5.7决定SQL中IN条件是否走索引的成本计算,mysql中的index dive是在两个区间之间计算有多少条记录的方式
    mysql5.7的SQL执行成本计算,IO成本和CPU成本,单表查询成本,多表连接查询成本,执行成本决定mysql是否走索引,.OPTIMIZER_TRACE,cost_info
    mysql5.7基于块的嵌套循环连接(Block Nested-Loop Join)
    时间复杂度计算法
    mysql5.7索引合并:交集、并集,EXPLAIN例子
    mysql5.7分区表
    mysql5.7的随机IO和顺序IO
    MySQL5.7二级索引+回表的MRR优化技术
  • 原文地址:https://www.cnblogs.com/linyujun/p/5205760.html
Copyright © 2011-2022 走看看