zoukankan      html  css  js  c++  java
  • 位运算技巧3

    检测一个无符号数是不为2^n-1(^为幂) x&(x+1)

    将最右侧0位改为1位: x | (x+1)

    二进制补码运算公式:

    -x = ~x + 1 = ~(x-1)

    ~x = -x-1

    -(~x) = x+1

    ~(-x) = x-1

    x+y = x - ~y - 1 = (x|y)+(x&y)

    x-y = x + ~y + 1 = (x|~y)-(~x&y)

    x^y = (x|y)-(x&y)

    x|y = (x&~y)+y

    x&y = (~x|y)-~x

    x==y:    ~(x-y|y-x)

    x!=y:    x-y|y-x

    x< y:    (x-y)^((x^y)&((x-y)^x))

    x<=y:    (x|~y)&((x^y)|~(y-x))

    x< y:    (~x&y)|((~x|y)&(x-y))//无符号x,y比较

    x<=y:    (~x|y)&((x^y)|~(y-x))//无符号x,y比较

    使用位运算的无分支代码:

    计算绝对值

    int abs( int x )

    {

        int y ;

        y = x >> 31 ;

        return (x^y)-y ;//or: (x+y)^y

    }

    符号函数:sign(x) = -1, x<0; 0, x == 0 ; 1, x > 0

    int sign(int x)

    {

        return (x>>31) | (unsigned(-x))>>31 ;//x=-2^31时失败(^为幂)

    }

    三值比较:cmp(x,y) = -1, x<y; 0, x==y; 1, x > y

    int cmp( int x, int y )

    {

        return (x>y)-(x-y) ;

    }

    doz=x-y, x>=y; 0, x<y

    int doz(int x, int y )

    {

        int d ;

        d = x-y ;

        return d & ((~(d^((x^y)&(d^x))))>>31) ;

    }

    int max(int x, int y )

    {

        int m ;

        m = (x-y)>>31 ;

        return y & m | x & ~m ;

    }

    不使用第三方交换x,y:

    1.x ^= y ; y ^= x ; x ^= y ;

    2.x = x+y ; y = x-y ; x = x-y ;

    3.x = x-y ; y = y+x ; x = y-x ;

    4.x = y-x ; x = y-x ; x = x+y ;

    双值交换:x = a, x==b; b, x==a//常规编码为x = x==a ? b :a ;

    1.x = a+b-x ;

    2.x = a^b^x ;

    下舍入到2k次方的倍数:

    1.x & ((-1)<<k)

    2.(((unsigned)x)>>k)<<k

    上舍入:

    1. t = (1<<k)-1 ; x = (x+t)&~t ;

    2.t = (-1)<<k ; x = (x-t-1)&t ;

    位计数,统计1位的数量:

    1.

    int pop(unsigned x)

    {

        x = x-((x>>1)&0x55555555) ;

        x = (x&0x33333333) + ((x>>2) & 0x33333333 ) ;

        x = (x+(x>>4)) & 0x0f0f0f0f ;

        x = x + (x>>8) ;

        x = x + (x>>16) ;

        return x & 0x0000003f ;

    }

    2.

    int pop(unsigned x) {

        static char table[256] = { 0,1,1,2, 1,2,2,3, ...., 6,7,7,8 } ;

        return table[x&0xff]+table[(x>>8)&0xff]+table[(x>>16)&0xff]+table[(x>>24)] ;

    }

    奇偶性计算:

    x = x ^ ( x>>1 ) ;

    x = x ^ ( x>>2 ) ;

    x = x ^ ( x>>4 ) ;

    x = x ^ ( x>>8 ) ;

    x = x ^ ( x>>16 ) ;

    结果中位于x最低位,对无符号x,结果的第i位是原数第i位到最左侧位的奇偶性位反转:

    unsigned rev(unsigned x)

    {

        x = (x & 0x55555555) << 1 | (x>>1) & 0x55555555 ;

        x = (x & 0x33333333) << 2 | (x>>2) & 0x33333333 ;

        x = (x & 0x0f0f0f0f) << 4 | (x>>4) & 0x0f0f0f0f ;

        x = (x<<24) | ((x&0xff00)<<8) | ((x>>8) & 0xff00) | (x>>24) ;

        return x ;

    }

    递增位反转后的数:

    unsigned inc_r(unsigned x)

    {

        unsigned m = 0x80000000 ;

        x ^= m ;

        if( (int)x >= 0 )

            do { m >>= 1 ; x ^= m ; } while( x < m ) ;

        return x ;

    }

    混选位:

    abcd efgh ijkl mnop ABCD EFGH IJKL MNOP->aAbB cCdD eEfF gGhH iIjJ kKlL mMnN oOpP

    unsigned ps(unsigned x)

    {

        unsigned t ;

        t = (x ^ (x>>8)) & 0x0000ff00; x = x ^ t ^ (t<<8) ;

        t = (x ^ (x>>4)) & 0x00f000f0; x = x ^ t ^ (t<<4) ;

        t = (x ^ (x>>2)) & 0x0c0c0c0c; x = x ^ t ^ (t<<2) ;

        t = (x ^ (x>>1)) & 0x22222222; x = x ^ t ^ (t<<1) ;

        return x ;

    }

    位压缩:

    选择并右移字x中对应于掩码m1位的位,如:compress(abcdefgh,01010101)=0000bdfh

    compress_left(x,m)操作与此类似,但结果位在左边: bdfh0000.

    unsigned compress(unsigned x, unsigned m)

    {

        unsigned mk, mp, mv, t ;

        int i ;

        x &= m ;

        mk = ~m << 1 ;

        for( i = 0 ; i < 5 ; ++i ) {

            mp = mk ^ ( mk << 1) ;

            mp ^= ( mp << 2 ) ;

            mp ^= ( mp << 4 ) ;

            mp ^= ( mp << 8 ) ;

            mp ^= ( mp << 16 ) ;

            mv = mp & m ;

            m = m ^ mv | (mv >> (1<<i) ) ;

            t = x & mv ;

            x  = x ^ t | ( t >> ( 1<<i) ) ;

            mk = mk & ~mp ;

        }

        return x ;

    }

    位置换:

    325位数表示从最低位开始的位的目标位置,结果是一个32*5的位矩阵,

    将该矩阵沿次对角线转置后用532位字p[5]存放。

    SAG(x,m) = compress_left(x,m) | compress(x,~m) ;

    准备工作:

    void init( unsigned *p ) {

        p[1] = SAG( p[1], p[0] ) ;

        p[2] = SAG( SAG( p[2], p[0]), p[1] ) ;

        p[3] = SAG( SAG( SAG( p[3], p[0] ), p[1]), p[2] ) ;

        p[4] = SAG( SAG( SAG( SAG( p[4], p[0] ), p[1]) ,p[2]), p[3] ) ;

    }

    实际置换:

    int rep( unsigned x ) {

        x = SAG(x,p[0]);

        x = SAG(x,p[1]);

        x = SAG(x,p[2]);

        x = SAG(x,p[3]);

        x = SAG(x,p[4]);

        return x ;

    }

    二进制码到GRAY码的转换:

    unsigned B2G(unsigned B )

    {

        return B ^ (B>>1) ;

    }

    GRAY码到二进制码:

    unsigned G2B(unsigned G)

    {

        unsigned B ;

        B = G ^ (G>>1) ;

        B = G ^ (G>>2) ;

        B = G ^ (G>>4) ;

        B = G ^ (G>>8) ;

        B = G ^ (G>>16) ;

        return B ;

    }

    找出最左0字节的位置:

    int zbytel( unsigned x )

    {

        static cahr table[16] = { 4,3,2,2, 1,1,1,1, 0,0,0,0, 0,0,0,0 } ;

        unsigned y ;

        y = (x&0x7f7f7f7f) + 0x7f7f7f7f ;

        y = ~(y|x|0x7f7f7f7f) ;

        return table[y*0x00204081 >> 28] ;//乘法可用移位和加完成

    }

       

    位运算 之(1 按位与(AND& 操作

    由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

     按位与(Bitwise AND),运算符号为&

    a&b 的操作的结果:ab中对应位同时为1,则对应结果位也为1

    例如:

    10010001101000101011001111000

    111111100000000

    ---------------------------------------------

                 10101100000000

    10101100000000进行右移8位得到的是101011,这就得到了a8~15位的掩码了。那么根据这个启示,判断一个整数是否是处于 0-65535 之间(常用的越界判断):

    用一般的 (a >= 0) && (a <= 65535) 可能要两次判断。

    改用位运算只要一次:

    a & ~((1 << 16)-1)

    后面的常数是编译时就算好了的。其实只要算一次逻辑与就行了。

    常用技巧:

     1  用于整数的奇偶性判断

     一个整数a, a & 1 这个表达式可以用来判断a的奇偶性。二进制的末位为0表示偶数,最末位为1表示奇数。使用a%2来判断奇偶性和a & 1是一样的作用,但是a & 1要快好多。

       

    2  判断n是否是2的正整数冪

     (!(n&(n-1)) ) && n

    举个例子:                            

    如果n = 16 = 10000 n-1 = 1111

    那么:

    10000

    & 1111

    ----------

                      0

    再举一个例子:如果n = 256 = 100000000 n-1 = 11111111

    那么:

    100000000

    &11111111

    --------------

    0

    好!看完上面的两个小例子,相信大家都有一个感性的认识。从理论上讲,如果一个数a他是2的正整数幂,那么a 的二进制形式必定为1000…..(后面有0个或者多个0),那么结论就很显然了。

       

    3  统计n1的个数

     朴素的统计办法是:先判断n的奇偶性,为奇数时计数器增加1,然后将n右移一位,重复上面步骤,直到移位完毕。

    朴素的统计办法是比较简单的,那么我们来看看比较高级的办法。

     举例说明,考虑2位整数 n=11,里边有21,先提取里边的偶数位10,奇数位01,把偶数位右移1位,然后与奇数位相加,因为每对奇偶位相加的和不会超过"两位",所以结果中每两位保存着数n1的个数;相应的如果n是四位整数 n=0111,先以"一位"为单位做奇偶位提取,然后偶数位移位(右移1位),相加;再以"两位"为单位做奇偶提取,偶数位移位(这时就需要移2位),相加,因为此时没对奇偶位的和不会超过"四位",所以结果中保存着n1的个数,依次类推可以得出更多位n的算法。整个思想类似分治法。

    在这里就顺便说一下常用的二进制数:

    0xAAAAAAAA=10101010101010101010101010101010

    0x55555555 = 1010101010101010101010101010101(奇数位为11位为单位提取奇偶位

       

    0xCCCCCCCC = 11001100110011001100110011001100

    0x33333333 = 110011001100110011001100110011(以"2"为单位提取奇偶位)

       

    0xF0F0F0F0 = 11110000111100001111000011110000

    0x0F0F0F0F = 1111000011110000111100001111"8"为单位提取奇偶位

       

    0xFFFF0000 =11111111111111110000000000000000

    0x0000FFFF = 1111111111111111"16"为单位提取奇偶位

       

    例如:32位无符号数的1的个数可以这样数:

    int count_one(unsigned long n)

    {

        //0xAAAAAAAA0x55555555分别是以"1"为单位提取奇偶位

        n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);

        //0xCCCCCCCC0x33333333分别是以"2"为单位提取奇偶位

        n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);

        //0xF0F0F0F00x0F0F0F0F分别是以"4"为单位提取奇偶位

        n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);

        //0xFF00FF000x00FF00FF分别是以"8"为单位提取奇偶位

        n = ((n & 0xFF00FF00) >> 8) + (n & 0x00FF00FF);

        //0xFFFF00000x0000FFFF分别是以"16"为单位提取奇偶位

        n = ((n & 0xFFFF0000) >> 16) + (n & 0x0000FFFF);

        return n;

    }

       

    举个例子吧,比如说我的生日是农历211,就用211吧,转成二进制:

                n = 11010011

    计算n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);

    得到        n = 10010010

    计算n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);

    得到        n = 00110010

    计算n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);

    得到        n = 00000101 -----------------à无法再分了,那么5就是答案了。

       

        

    4、对于正整数的模运算注意,负数不能这么算

       

    先说下比较简单的:

    乘除法是很消耗时间的,只要对数左移一位就是乘以2,右移一位就是除以2,传说用位运算效率提高了60%

    2^k 众所周知: n<<k。所以你以后还会傻傻地去敲2566*4的结果10264吗?直接2566<<4就搞定了,又快又准确。

       

    2^k众所周知: n>>k

       

    那么 mod 2^k 呢?(2的倍数取模

    n&((1<<k)-1)

    用通俗的言语来描述就是,2的倍数取模,只要将数与2的倍数-1做按位与运算即可。

    好!方便理解就举个例子吧。

    思考:如果结果是要求模2^k时,我们真的需要每次都取模吗?

       

    在此很容易让人想到快速幂取模法。

    快速幂取模算法

    经常做题目的时候会遇到要计算 a^b mod c 的情况,这时候,一个不小心就TLE了。那么如何解决这个问题呢?位运算来帮你吧。

       

    首先介绍一下秦九韶算法:(数值分析讲得很清楚)

    把一个n次多项式f(x) = a[n]x^n+a[n-1]x^(n-1)+......+a[1]x+a[0]改写成如下形式:

      f(x) = a[n]x^n+a[n-1]x^(n-1))+......+a[1]x+a[0]

      = (a[n]x^(n-1)+a[n-1]x^(n-2)+......+a[1])x+a[0]

      = ((a[n]x^(n-2)+a[n-1]x^(n-3)+......+a[2])x+a[1])x+a[0]

      =. .....

      = (......((a[n]x+a[n-1])x+a[n-2])x+......+a[1])x+a[0].

      求多项式的值时,首先计算最内层括号内一次多项式的值,即

      v[1]=a[n]x+a[n-1]

      然后由内向外逐层计算一次多项式的值,即

      v[2]=v[1]x+a[n-2]

      v[3]=v[2]x+a[n-3]

      ......

      v[n]=v[n-1]x+a[0]

    这样,求n次多项式f(x)的值就转化为求n个一次多项式的值。

       

    好!有了前面的基础知识,我们开始解决问题吧

    (a × b) mod c=( (a mod c) × b) mod c.

    我们可以将 b先表示成就:

      b = a[t] × 2^t + a[t-1]× 2^(t-1) + …… + a[0] × 2^0. (a[i]=[0,1]).

    这样我们由 a^b mod c = (a^(a[t] × 2^t + a[t-1] × 2^t-1 + …a[0] × 2^0) mod c.

    然而我们求  a^( 2^(i+1) ) mod c=( (a^(2^i)) mod c)^2 mod c .求得。

    具体实现如下:

    使用秦九韶算法思想进行快速幂模算法,简洁漂亮

    // 快速计算 (a ^ p) % m 的值

    __int64 FastM(__int64 a, __int64 p, __int64 m)

        if (p == 0return 1;

        __int64  r = a % m;

        __int64  k = 1;

        while (p > 1)

        {

            if ((p & 1)!=0)

            {

                k = (k * r) % m; 

    }

                  r = (r * r) % m;

                p >>= 1;

            }

            return (r * k) % m;

    }  http://acm.pku.edu.cn/JudgeOnline/problem?id=3070

     5、计算掩码

    比如一个截取低6位的掩码:0×3F

    用位运算这么表示:(1 << 6) - 1

    这样也非常好读取掩码,因为掩码的位数直接体现在表达式里。

     按位或运算很简单,只要ab中相应位出现1,那么a|b的结果相应位也为1。就不多说了。 

     6、子集

      枚举出一个集合的子集。设原集合为mask,则下面的代码就可以列出它的所有子集: 

      for (i = mask ; i ; i = (i - 1) & mask) ; 

      很漂很漂亮吧。

            此部分来自:http://blog.csdn.net/g_spider/article/details/5750665

    -----------------------------------------------------------------------------------

    实例:

    public class d2binary
    {
    public static void main(String []args)
    {
    int a=10;
    for(int i=31;i>=0;i--)
    {
    System.out.print(a>>i&1); //输出二进制
    }
    System.out.print("
    
    ");
     
    d2binary op=new d2binary();
     
    int b=15;
    int c;
     
    c=op.add(1,1);
    System.out.println("
    Add(10,15)-->c="+c);
     
    }
     
     
     
    //从32位的单元中取出某几位
    public int getMidBits(int val,int n1,int n2)
    {
    int z;
    z=~0; //将z初始化16位的1
    z=(z>>n1)&(z<<(32-n2)); //将两端的化成0,中间的化成1
    z=val&z;
    z=z>>(32-n2);
    return z;
    }
     
    //对32的二进制数取出它的奇数位(从左边起1,3,5 。。。)
    public int getOddBits(int val)
    {
    int z,a,q;
    z=0;
    for(int i=1;i<=31;i+=2)
    {
    q=1;
    for(int j=1;j<=(32-i-1)/2;j++) //要取的数的位数为原来的数的位数的1/2
    q=q*2; //原数进位指针进两位,要取的数的指针进一位
    a=val >> (32-i); //将第i个位置的数移到最低位
    a=a << 31; //通过左移31位,将将最低位移到最高位去,其后的位全都补0
    a=a >> 31; //右移31位,将最高位移到最低,其前面的位全都补零,得到第i位
    z=z+a*q; //积加取出的数
    }
    return z;
    }
     
    //算术右移:低位溢出,符号位不变,并用符号位补溢出的高位
    //算术左移:符号位不变,低位补0
    //逻辑右移:低位溢出,高位补零
     
    //实现算术右移函数
    public int shiftRightArithmetic(int val,int n)
    {
    int z;
    z=~0;
    z=z>>n;
    z=~z;
    z=z|(val >> n);
    return z;
    }
     
    //实现逻辑右移函数
    public int shiftRightLogical(int val,int n)
    {
    int z;
    z=(~(1 >> n))&(val >> n);
    return z;
    }
     
    //实现右在循环移位
    public int moveRightCircle(int val,int n)
    {
    int z;
    z=(val >> n)|(val << (32-n));
    return z;
    }
     
    //实现左循环移位
    public int moveLeftCircle(int val,int n)
    {
    int z;
    z=(val >> (32-n))|(val << n);
    return z;
    }
     
    //根据原码求补码(求二进制数的补码)
    public int realBits2MaskBit(int val)
    {
    int z;
    z=val&10000000;
    if(z==10000000)
    z=~val+1;
    else
    z=val;
    return z;
    }
    //一个正数的补码等于该数的原码,一个负数的补码等于该数的反码加1
    }
     
     
     
     
    //螺旋数组问题
    。。。。。。。。
    public class d2binary
    {
    public static void main(String []args)
    {
    int a=10;
    for(int i=31;i>=0;i--)
    {
    System.out.print(a>>i&1); //输出二进制
    }
    System.out.print("
    
    ");
     
    d2binary op=new d2binary();
     
    int b=15;
    int c;
     
    c=op.add(1,1);
    System.out.println("
    Add(10,15)-->c="+c);
     
    }
     
     
     
    //从32位的单元中取出某几位
    public int getMidBits(int val,int n1,int n2)
    {
    int z;
    z=~0; //将z初始化16位的1
    z=(z>>n1)&(z<<(32-n2)); //将两端的化成0,中间的化成1
    z=val&z;
    z=z>>(32-n2);
    return z;
    }
     
    //对32的二进制数取出它的奇数位(从左边起1,3,5 。。。)
    public int getOddBits(int val)
    {
    int z,a,q;
    z=0;
    for(int i=1;i<=31;i+=2)
    {
    q=1;
    for(int j=1;j<=(32-i-1)/2;j++) //要取的数的位数为原来的数的位数的1/2
    q=q*2; //原数进位指针进两位,要取的数的指针进一位
    a=val >> (32-i); //将第i个位置的数移到最低位
    a=a << 31; //通过左移31位,将将最低位移到最高位去,其后的位全都补0
    a=a >> 31; //右移31位,将最高位移到最低,其前面的位全都补零,得到第i位
    z=z+a*q; //积加取出的数
    }
    return z;
    }
     
    //算术右移:低位溢出,符号位不变,并用符号位补溢出的高位
    //算术左移:符号位不变,低位补0
    //逻辑右移:低位溢出,高位补零
     
    //实现算术右移函数
    public int shiftRightArithmetic(int val,int n)
    {
    int z;
    z=~0;
    z=z>>n;
    z=~z;
    z=z|(val >> n);
    return z;
    }
     
    //实现逻辑右移函数
    public int shiftRightLogical(int val,int n)
    {
    int z;
    z=(~(1 >> n))&(val >> n);
    return z;
    }
     
    //实现右在循环移位
    public int moveRightCircle(int val,int n)
    {
    int z;
    z=(val >> n)|(val << (32-n));
    return z;
    }
     
    //实现左循环移位
    public int moveLeftCircle(int val,int n)
    {
    int z;
    z=(val >> (32-n))|(val << n);
    return z;
    }
     
    //根据原码求补码(求二进制数的补码)
    public int realBits2MaskBit(int val)
    {
    int z;
    z=val&10000000;
    if(z==10000000)
    z=~val+1;
    else
    z=val;
    return z;
    }
    //一个正数的补码等于该数的原码,一个负数的补码等于该数的反码加1
    }
     
    
    //螺旋数组问题
    。。。。。。。。

    更多:

    http://blog.acmj1991.com/?p=214

    http://www.cppblog.com/xiaoyisnail/archive/2009/09/19/96707.html

  • 相关阅读:
    sp2010 升级sp2013 用户无法打开网站
    powerviot install in sharepoint 2013
    can not connect cube in performancce dashboard
    westrac server security configure user info
    添加报表服务在多服务器场
    sharepoint 2013 office web app 2013 文档在线浏览 IE11 浏览器不兼容解决方法
    delete job definition
    目前付款申请单内网打开慢的问题
    item style edit in sharepoint 2013
    Could not load file or assembly '$SharePoint.Project.AssemblyFullName$'
  • 原文地址:https://www.cnblogs.com/youxin/p/3297675.html
Copyright © 2011-2022 走看看