进制学习指南

前置芝士

集合

其中 &表示按位与,∣ 表示按位或,⊕表示按位异或,∼ 表示按位取反。

[集合与集合]

img

[集合与元素]

通常会用到移位运算。其中 <<表示左移,>>表示右移。左移 i 位相当于乘 \(2^i\),右移 i 位相当于除 \(2^i\)

img

[证明]

删除最小元素

    s = 101100
    s-1 = 101011 // 最低位的 1 变成 0,同时 1 右边的 0 都取反,变成 1
    s&(s-1) = 101000

二进制运算技巧

  • 利用异或操作 ^ 和空格进行英文字符大小写互换

    ('d' ^ ' ') = 'D'
    ('D' ^ ' ') = 'd'
    
  • 消除数字 n 的二进制表示中的最后一个 1

    n&(n-1)
    
  • 判断两个数是否异号

    int x = -1, y = 2;
    bool f = ((x ^ y) < 0); // true
    
    int x = 3, y = 2;
    bool f = ((x ^ y) < 0); // false
    
    
  • 判断一个数是不是 2 的指数

    bool isPowerOfTwo(int n) {
        if (n <= 0) return false;
        return (n & (n - 1)) == 0;
    }
    
  • 不进位加法

    a^b
    
  • 进位加法

    a + b = (a ^ b) + ((a & b) << 1)
    public int plus(int a, int b) {
            if (b == 0)
                return a;
            int _a = (a ^ b);
            int _b = ((a & b) << 1);
            return plus(_a, _b);
    }
    

    7.数组中的数字只有一个出现了一次,其他的都出现了两次,找出这个只出现一次的数字。

    public int singleNumber(int[] nums) {
            int result = 0, n = nums.length;
            for (int i = 0; i < n; i++)
            {
                result ^= nums[i];
            }
            return result;
    }
    
  • 取x最后一位1的位置

    x&(-x)
    
  • 去掉x的最后一位1

    x&(-x)
    
  • 把二进制的某一位置1或者置0

    a | (1 << n)//将a的第n位置1
    a & ~(1 << n)//把第n位置0
    
  • 判断某一位是否为1

    a & (1 << n)
    
  • 补码

    x&0xffffffff
    
  • //把两个32位数合并成一个64位数

    long t = (ll) target[0] << 32 | target[1];
    

特别二进制表示

0xaaaaaaaa 表示'10101010101010101010101010101010'(32位)奇数位为1
0x55555555 表示'1010101010101010101010101010101'(31位)偶数位为1

一个数的倍数的二进制中的1出现次数的规律是与该数的二进制表示中1的个数有关

假设我们有一个数n,它的二进制表示中有m个1。那么n的倍数k的二进制表示中的1的个数可以通过以下规律计算:

  1. 如果k是n的倍数,那么k的二进制表示中的1的个数等于n的二进制表示中1的个数。
  2. 如果k不是n的倍数,那么k的二进制表示中的1的个数比n的二进制表示中1的个数多。

雷格码

n 位格雷码序列 是一个由 \(2^n\) 个整数组成的序列,其中:每个整数都在范围 \([0, 2^{n- 1}]\) 内。

  • 第一个整数是 0
  • 一个整数在序列中出现 不超过一次
  • 每对 相邻 整数的二进制表示 恰好一位不同 ,且
  • 第一个最后一个 整数的二进制表示 恰好一位不同

[构造长度为n的雷格码]

 G(i) = i ^ (i/2);
	n = 3: 
        G(0) = 000, 
        G(1) = 1 ^ 0 = 001 ^ 000 = 001
        G(2) = 2 ^ 1 = 010 ^ 001 = 011 
        G(3) = 3 ^ 1 = 011 ^ 001 = 010
        G(4) = 4 ^ 2 = 100 ^ 010 = 110
        G(5) = 5 ^ 2 = 101 ^ 010 = 111
        G(6) = 6 ^ 3 = 110 ^ 011 = 101
        G(7) = 7 ^ 3 = 111 ^ 011 = 100

【c++】

    vector<int> grayCode(int n) {
        vector<int> res;
        for(int i=0;i<1<<n;i++){
            res.push_back(i^i>>1);
        }
        return res;
    }

集合操作全集

集合大小

//集合大小,即二进制表示中1的个数
int x=__builtin_popcount(2);//1

遍历集合

设元素范围从 0 到 n1,从空集 ∅ 枚举到全集 U

//c++
for (int s = 0; s < (1 << n); s++) {
    // 处理 s 的逻辑
}

设集合为 s从大到小枚举 s 的所有非空子集 sub

//c++
for (int sub = s; sub; sub = (sub - 1) & s) {
    // 处理 sub 的逻辑
}

[证明]

sub = (sub - 1) & s

为什么要写成 sub = (sub - 1) & s 呢?

暴力做法是从s 出发,不断减一直到0,但这样中途会遇到很多并不是s 的子集的情况。例如s=10101 时,减一得到10100,这是s 的子集。但再减一就得到10011 了,这并不是s 的子集,下一个子集应该是10001。

把所有的合法子集按顺序列出来,会发现我们做的相当于「压缩版」的二进制减法,例如

10101→10100→10001→10000→00101→⋯

如果忽略掉10101 中的两个0,数字的变化和二进制减法是一样的,即

111→110→101→100→011→⋯

如何快速找到下一个子集呢?以10100→10001 为例说明,普通的二进制减法会把最低位的1 变成0,同时1 右边的0 变成1,即10100→10011。「压缩版」的二进制减法也是类似的,把最低位的1 变成0,但同时对于1 右边的0,只保留在s=10101中的1,所以是10100→10001。怎么保留?& 10101 就行。

如果要从大到小枚举 s 的所有子集 sub(从 s 枚举到空集),可以这样写:

int sub = s;
do {
    // 处理 sub 的逻辑
    sub = (sub - 1) & s;
} while (sub != s);

当sub=0时(空集),再减一就得到-1,对应的二进制位111···1,再&x就得到了s,所以sub=s时说明最后一次循环的是空集,退出循环。

枚举所有大小恰好为k的子集

1)求出最低位的1开始的连续1的区间

2)将区间全部变为0,并将区间左侧的那个0变为1

3)将第1步取出的区间右移,直到剩下的1的个数减少了1个

4)将第2步和第三步的结果按位取或

举例:

k=4,comb=0101110->0110011

x=0100010,y=0110000

1)0101110->0001110

2)0101110->0110000

3)0001110->0000011

4)0110000||0000011->0110011

int comb=(1<<k)-1;//最小的子集
while(comb<1<<n){
   //x为最低位的1,y:将cmob从最低位的1开始的连续的1都置0,并左一位加1.
   int x=comb&-comb,y=comb+x;
   //z=comb&-y:的得到了最低位1开始的连续区间,/x:将z右移,直到最低位为1,>>1:z的1的个数减少1个,
   comb=((comb&-y)/x>>1)|y;
}

枚举不包含相邻元素的子集

进制转换

x :2-36的任何进制(int)(36是因为10个数字+26个英文字母,所以最多有36进制)

y :2-36的任何进制(int)

a :2-36的任何进制表示的数(string)
string divide(int x,string a,int y)

int charToInt(char x){
    if(x<='9')return x-'0';
    else return x-'A'+10;
}
char intToChar(int x){
    if(x<=9)return x+'0';
    else return x-10+'A';
}
string divide(int x,string a,int y){
    string res;
    while(a.size()!=0){
        int remainder=0;
        for(int i=0;i<a.size();i++){        //    a/y一次
            int num=remainder*x+charToInt(a[i]);
            a[i]=intToChar(num/y);
            remainder=num%y;
        }
        res+=(intToChar(remainder));       //   res加上最后一位的余数
        int pos=0;
        while(a[pos]=='0')pos++;//因为字符串除法有前导0存在的可能,所以把‘0’去掉
        a=a.substr(pos);
    }
    reverse(res.begin(),res.end());       //因为是相反的,随意最后进行反向。
    return res;
}
void solve(){
int x,y;string a;
    while(cin>>x>>a>>y){
        for(int i=0;i<a.size();i++){
            if(a[i]>='a'&&a[i]<='z')
                a[i]=a[i]-'a'+'A';
        }
        cout<<divide(x,a,y)<<endl;
    }
}

负二进制加法

arr1=[1,1,1,1,1] arr2=[1,0,1]

1)等位向加,如果x大于等于2,向前进位-1,因为在以负二进制为基地中,相邻位是一正一负。

2)如果carry=-1,但此位上都为0,则将此位上赋值为1,进位+1,例如\((2^0+2^0)=(2^2)-(2^1)\)

3)最后将ans数组去除前导零,逆序输出,即为答案。

//就是普通的模拟进制,向前进位是-1,然后知道-1 == 11
    vector<int> addNegabinary(vector<int>& arr1, vector<int>& arr2) {
        int i = arr1.size() - 1, j = arr2.size() - 1;
        int carry = 0;
        vector<int> ans;
        while (i >= 0 || j >= 0 || carry) {
            int x = carry;
            if (i >= 0) {
                x += arr1[i];
            }
            if (j >= 0) {
                x += arr2[j];
            }
  
            if (x >= 2) {
                ans.push_back(x - 2);
                carry = -1;
            }
            else if (x >= 0) {
                ans.push_back(x);
                carry = 0;
            }
            else {
                ans.push_back(1);
                carry = 1;
            }
            --i;
            --j;
        }
        while (ans.size() > 1 && ans.back() == 0) {
            ans.pop_back();
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }

九进制特性

(1)移除所有包含数字9的数字,给我们一个数字n,让我们求在这个新的数组中第n个数字.

10,11,12,13,14,15,16,17,18 (移除了19,20.....80,81,82,83,84,85,86,87,88 (移除了89)......100

res:可以将十进制数n转为九进制数.

(2)移除所有包含数字k(1<=x<9)的数字,给我们一个数字n,让我们求在这个新的数组中第n个数字.

res:可以先将十进制n转为九进制数,让后观察每位数字,判断是否大于等于k,如果大于等于k,将这一位加1.

posted @ 2023-10-17 13:56  White_Sheep  阅读(11)  评论(0编辑  收藏  举报