康托展开与逆康托展开(转载)

一.引出康托展开

​ 动态规划题有一类分支叫状压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 = 0;
    for (int i = 0; i < k; i ++) {
        int cnt = 0;
        for (int j = i + 1; j < k; j++) {
            if (s[i] > s[j]) cnt++;
        }
        num += fact[k - i - 1] * cnt;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(2)康托逆展开代码:

fact[10];	//fact[i]存储i的阶乘的值
//把状态值num转回数组s
bool book[10];		//判断序列中下角标为i的数是否已经标记
void inv_cantor (int s[], ll num, int k) {
    memset (book, 0, sizeof(book));
    for (int i = 0; i < k; i++) {
        int p = num / fact[k - i - 1];
        num %= fact[k - i - 1];
        int tot = 0;
        for (int j = 0; j < k; j++) {
            if (!book[j]) tot ++;
            if (tot == p) {
                book[j] = 1;
                s[i] = j + 1;
                break;
            }
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值