m位二进制数,1个数不超过n,字典序排下第i个数

本文探讨了一种高效算法,用于生成指定m位二进制数中1的个数不超过n的第i个数。通过采用二叉树方式、生成排列和优化搜索过程,实现线性复杂度生成目标数。重点介绍了算法的复杂度分析及其实现细节。

题目:给定m位字符串数组S,由0和1组成,要求m位中1的个数不超过n,然后找到字典序的第i个数。如:如 m=4 n=2 即1的个数不超过2个  0111就不行,如果找第i=8个 从0000  0001 …… 0110 1000 。第八个就是1000 。写一个函数,输入m,n,i返回那个数。


思路1:

m=4,n=2,i=8,按正常的方法来讲是16中方法,晒去不合格的,即1的数量超过2的,那么还剩下11个,因此取第8个为1000:0000、0001、0010、0011、0100、0101、0110、1000、1001、1010、1100。m=4的情况,则数的大小应该是在0~2^4-1之间,可以判断该数的二进制中1的个数,那这个方法的复杂度是:O(2^n)。

思路2:

采用二叉树的方式,这种方法主要耗费的时间在根据规则创建合法的二叉树上,当到达一个节点时,此时字符串中已经包含的1的个数为k,如果k<n,那么该节点的子节点就有两种选择,0或者1,如果k=n,则只有一个选择,后边的所有元素只能为0,这样最后数叶节点那一层,数第i个就可以了。

思路3:生成排列,首先按照1的思路,对于二进制数的递增序列,其实就是所代表的十进制数的递增序列,而递增都是从最后一位开始增长的。比如:0000->0001->0010。对于这样的序列,要实现递增通常有两种操作:

1)如果末位是0,那么将0变成1,这里1的个数是增加的;2)如果末位是1,那么将1变成0,然后实现进位操作,操作过程中1的个数不变。

对于0011,按照2)操作,变成了0110,实现了递增。因此通过1)和2)两种操作,能实现构造字典序的含指定个数1的01字符。2)的操作会遇到一种特殊情况,比如从0110下一个就是0111,1的个数已经超了,在下一个1000是合法的,那么如何直接从0110构造1000呢?需要对2)操作进行修改,在1的个数等于n的情况下,“末位”指从右往左第一个非零位,因此0110通过2)操作变成了1000 。将算法的伪代码描述如下:


bit[],m,n,i
permutation{
    k,j,num,h,last=m-1
    for(k=1;k<i;){
        num = ones_num(bit,m);
        if(num<n){
                if(bit[last]==0)
                    1)操作
                else
                    2)操作
        }else{
                j=m-1;
                while(j>=0 && bit[j]==0)
                    j--;
                h = j;
                while(h>=0&&bit[h]==1)
                    h--;
                if(h<0)
                    break;
                else if(bit[h]==0){
                    2)操作
                }
            }
    }
}

贴出C++代码:

#include<iostream>
using namespace std;
#define M 10
int bit[M];

int ones_num(int arr[],int len){
    int cnt=0;
    for(int i=0;i<len;i++)
        cnt += arr[i];
    return cnt;
}

void permutation(int m,int n,int i){
    memset(bit,0,sizeof(bit));
    int k,j,num,h;
    for(k=1;k<i;){
        num = ones_num(bit,m);
        if(num<n){
                if(bit[m-1]==0){
                    bit[m-1] = 1;
                    k++;
                }else{
                    bit[m-1] = 0;
                    j = m-2;
                    while(j>=0&&bit[j]==1){
                        bit[j] = 0;
                        j--;
                    }
                    if(j>=0&&bit[j]==0){
                        bit[j] = 1;
                        k++;
                    }
                }
        }else if(num==n){
                j=m-1;
                while(j>=0 && bit[j]==0)
                    j--;
                h = j;
                while(h>=0&&bit[h]==1)
                    h--;
                if(h<0)
                    break;
                else if(bit[h]==0){
                    bit[j] = 0;
                    j--;
                    while(j>h){
                        bit[j] = 0;
                        j--;
                    }
                    bit[h] = 1;
                    k++;
                }
            }
    }
    for(j=0;j<m;j++)
        cout << bit[j]<< " ";
}


int main(){
    permutation(4,2,9);
    return 0;
}


这种生成排列的方法的关键之处就如0110的下一个排列使1000之处,能够省去中间大量的使1的总个数超过1的情况,时间复杂度为线性的。


帮我解释一下这题怎么推导出来的考虑答案的每一 # P5657 [CSP-S2019] 格雷码 ## 题目描述 通常,人们习惯将所有 $n$ 二进制串按照字典序排列,例如所有 2 二进制串按字典序从小到大排列为:00,0110,11。 格雷码(Gray Code)是一种特殊的 $n$ 二进制串排列法,它要求相邻的两个二进制串间**恰好**有一**同**,特别地,第一个串与最后一个串也算作相邻。 所有 2 二进制串按格雷码排列的一个例子为:00,011110。 $n$ 格雷码止一种,下面给出其中一种格雷码的生成算法: 1. 1 格雷码由两个 1 二进制串组成,顺序为:0,1。 2. $n + 1$ 格雷码的前 $2^n$ 个二进制串,可以由依此算法生成的 $n$ 格雷码(总共 $2^n$ 个 $n$ 二进制串)按**顺序**排列,再在每个串前加一个前缀 0 构成。 3. $n + 1$ 格雷码的后 $2^n$ 个二进制串,可以由依此算法生成的 $n$ 格雷码(总共 $2^n$ 个 $n$ 二进制串)按**逆序**排列,再在每个串前加一个前缀 1 构成。 综上,$n + 1$ 格雷码,由 $n$ 格雷码的 $2^n$ 个二进制串按顺序排列再加前缀 0,和按逆序排列再加前缀 1 构成,共 $2^{n+1}$ 个二进制串。另外,对于 $n$ 格雷码中的 $2^n$ 个 二进制串,我们按上述算法得到的排列顺序将它们从 $0 \sim 2^n - 1$ 编号。 按该算法,2 格雷码可以这样推出: 1. 已知 1 格雷码为 0,1。 2. 前两个格雷码为 00,01。后两个格雷码为 1110。合并得到 00,011110,编号依次为 0 ~ 3。 同理,3 格雷码可以这样推出: 1. 已知 2 格雷码为:00,011110。 2. 前四个格雷码为:000,001,011,010。后四个格雷码为:110,111101100。合并得到:000,001,011,010,110,111101100,编号依次为 0 ~ 7。 现在给出 $n$,$k$,请你求出按上述算法生成的 $n$ 格雷码中的 $k$ 号二进制串。 ## 输入格式 仅一行两个整数 $n$,$k$,意义见题目描述。 ## 输出格式 仅一行一个 $n$ 二进制串表示答案。 ## 输入输出样例 #1 ### 输入 #1 ``` 2 3 ``` ### 输出 #1 ``` 10 ``` ## 输入输出样例 #2 ### 输入 #2 ``` 3 5 ``` ### 输出 #2 ``` 111 ``` ## 输入输出样例 #3 ### 输入 #3 ``` 44 1145141919810 ``` ### 输出 #3 ``` 00011000111111010000001001001000000001100011 ``` ## 说明/提示 【样例 1 解释】 2 格雷码为:00,011110,编号从 0∼3,因此 3 号串是 10。 【样例 2 解释】 3 格雷码为:000,001,011,010,110,111101100,编号从 0∼7,因此 5 号串是 111。 【数据范围】 对于 $50\%$ 的数据:$n \leq 10$ 对于 $80\%$ 的数据:$k \leq 5 \times 10^6$ 对于 $95\%$ 的数据:$k \leq 2^{63} - 1$ 对于 $100\%$ 的数据:$1 \leq n \leq 64$, $0 \leq k \lt 2^n$ 第0为011001100110... 第1为0011110000111100... 发现第i即k⊕⌊ 2 k ​ ⌋的第i #include<iostream> int n; unsigned long long k; int main(){ std::cin>>n>>k; k^=k>>1; while(~--n)std::cout<<(k>>n&1); }
09-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值