zoukankan      html  css  js  c++  java
  • 位运算

    位运算

    1.知识

    1. bit 是度量信息的单位,包含0和1两种状态。计算机的各种运算最后无不归结为一个个bit的变化。

    符号 含义
    &
    |
    ~ 按位取反
    ^ 异或
    >> 右移(算术右移,高位补符号)
    << 左移(算术左移,低位补0)
    1. 按位取反举例
      补码的补码就是原码,在计算机中存储全是补码,
      十进制数1的二进制补码为:00000001
      取反后补码为:11111110
      求这个补码的原码:10000010(屏幕上显示都是原码-2) 所以1取反后等于-2

    2. 在补码下,每个数值都有唯一的表示方式,并且任意两个数值做加减法运算,都等价于在32位补码下做最高位不进位的二进制加减法运算。发生算术溢出时,相当于自动对232取模。所以要注意两个大整数相加有可能是负数。

    例题1

    求a的b次方对p取模的值,其中1<=a,b,p<=109. (POJ1995 raising modulo numbers)

    每个正整数可以唯一表示为若干指数不重复的2的次幂的和。也就是说如果b在二进制表示下有k位,其中第i位的数字是ci,那么:
    b=ck-12k-1+ck-22k-2+ ... +c020

    则:$ a^b = a^{c_{k-1}*2^{k-1}}*a^{c_{k-2}*2^{k-2}}*...*a^{c_0*2^0}$

    又因为$ k=lceil log (b+1) ceil $

    所以上式乘积项的数量不多于k个,算法为log级

    $ a^{2^i}=(a^{2^{i-1}})^2 $

    所以我们可以通过k次递推求出每个乘积项,当ci=1时,把该项累积到答案中。b&1运算可以取出b在二进制表示的最低位,而b>>1运算可以舍去最低位,在递推的过程中将二者结合,就可以遍历b在二进制表示下的所有数位ci.

    int pow(int a,int b,int p)
    {
    	int ans = 1 % p;
    	a %= p;
    	for(;b;b>>=1)
    	{
    		if(b&1) ans=(long long) ans * a % p;
    		a = (long long) a * a % p;
    	}
    	return ans;
    }
    

    例题2

    求a乘b对p取模的值,1<=a,b,p<=1018

    类似于快速幂:

    $ a*b=c_{k-1}*a*2^{k-1}+c_{k-2}*a*2^{k-2}+...+c_0*a*2^0 $

    因为$ a*2^i=(a*2^{i-1})*2 $,如果已经求出来$ a*2^{i-1}\% p$,则可以利用递推求出$ a*2^{i}\% p$,运算过程中每一步的结果都不超过2*1018,可以利用k次递推求出每个乘积项。当ci=1时,把该乘积项累加到答案中即可。

    typedef long long ll;
    ll mul(ll a,ll b,ll p)
    {
    	ll ans=0;
    	for(;b;b>>=1)
    	{
    		if(b&1) ans=(ans+a)%p;
    		a=a*2%p;
    	}
    	return ans;
    }
    

    当然,也可以利用$ a*b\%p=a*b-lfloor a*b/p floor *p $
    首先在a,b<p时,c=a*b/p向下取整以后一定小于p。我们可以用浮点数执行a*b/p的运算,而不用关心小数点后的部分。long double 在十进制下的有效数字有18~19位,足够胜任。当浮点数的精度不足时,它会像科学计数法一样舍弃低位,正好符合我们的要求。

    另外,a*b和c*p可能很大,但是二者的差一定在0~p-1之间,我们只关心它们较低的若干位即可。用long long保存结果,整数运算溢出相当于舍弃高位,也正好符合要求。

    typedef long long ll;
    ll mul(ll a,ll b,ll p)
    {
    	a %= p; b %= p;
    	long long c=(long double) a*b/p;
    	long long ans= a*b-c*p;
    	if(ans<0) ans+=p;
    	else if(ans>=p) ans-=p;
    	return ans;
    }
    

    2.成对变换

    我们通过计算可以发现,对于非负整数n:

    当n为偶数时,n ^ 1 等于 n+1

    当n为奇数时,n ^ 1 等于 n-1

    因此0和1,2和3,4和5,这些数对都是关于异或“成对变换”,这个性质经常用于图论,可以通过异或运算获得当前边的反向边编号。但为了安全起见,最好边的编号最好从2开始。

    3.lowbit运算

    lowbit(n) 取出非负整数n在二进制表示下最低位的1以及后边的0构成的数值。

    设n>0,n的第k位是1,第0k-1位都是0。为了实现lowbit,先把n取反,此时第k位变成0,第0k-1都是1.再令n=~n+1,此时因为进位,第k位变为1,第0~k-1变成0.

    由此可知,n和~n+1在第k+1到最高位的每一位都刚好相反,第k位都是1,第0到第k-1位都是0,这样一来,n & ~n+1 之后,就剩下第k位的1,其余位都是0.而在补码表示下,~n = -1-n,因此:
    lowbit(n) = n & (~n+1) = n & -n

    lowbit 是树状数组的核心操作。

    lowbit 配合hash可以找到整数二进制表示下所有是1的位,所花费的时间与1的个数同级。

    const int maxn_n = 1 << 20;
    int H[maxn_n];
    int n;
    int main()
    {
    	for(int i=0;i<=20;i++) H[1<<i]=i; //预处理出某个数字在第几位
    	while(cin >> n)
    	{
    		while(n>0)
    		{
    			cout<<H[n & -n] << ' ';
    			n-=n&-n;
    		}
    	}
    }
    

    4.二进制状态压缩

    二进制状态压缩,是指将一个长度为m的bool数组用一个m位二进制整数表示并存储的方法。利用下列位运算操作可以实现原bool数组中对应下标元素的存取。

    操作 运算
    取出整数n在二进制表示下的第k位 (n>>k) & 1
    取出整数n在二进制表示下第0~k-1位(后k位) n & ((1<<k)-1)
    把整数n在二进制表示下的第k位取反 n ^ (1<<k)
    对整数n在二进制表示下的第k位赋值为1 n | (1<<k)
    对整数n在二进制表示下的第k位赋值为0 n & (~(1<<k))

    这种方法运算简便,并且节省了程序运行的时间和空间。当m不大时,可以直接使用一个整数类型存储。当m较大时,可以使用若干个整数类型(int数组),也可以直接利用bitset实现。

    例题3

    给定一张n(n<=20)个点的带权无向图,点从0~n-1标号,求起点0到终点n-1的最短hamilton路径。hamilton路径的定义是从0到n-1不重不漏地经过每个点恰好一次。(洛谷1171,扩展题目:POJ2288 Islands and Bridges

    思路点拨:
    很容易想到一种朴素的做法,就是枚举n个点的全排列,计算路径长度,取最小值,时间复杂度为O(nn!),可以使用状态压缩dp将其优化到O(n22n)

    在任意时刻,要表示出哪些点被走过,哪些点没有被走过,可以用一个n位二进制数,若其第i位(0<=i<n)为1,则表示第i个点已经被经过,反之未被经过。在任意时刻还需要知道当前所处的位置,因此使用Fi,j表示点被经过的状态对应的二进制数为i,且目前处于点j时的最短路径。

    初始值F[1,0],即只经过了点0(i只有第0位是1),目前处于起点0,最短路径长度为0。方便起见,将F的其他值设为极大值,目标状态为F[(1<<n)-1,n-1],即经过所有点(i的所有位都是1),处于终点n-1的最短路。

    在任意时刻,有公式F[i,j]=min{F[i ^ (1<<j),k]+weight(k,j)},0<=k<n并且((i>>j)&1)=1,即当前时刻“被经过的点的状态”对应的二进制数为i,处于点j。因为j只能恰被经过1次,所以一定是刚刚经过的,故在上一时刻“被经过的点的状态”对应的二进制数的第j位应该赋值为0,也就是i(1<<j)。另外,上一时刻所处的位置可能是i(1<<j)中任意一个是1的位置k,从k走到j需要weight(k,j),可以考虑取所有这样的k取最小值。

    int f[1<<20][20];
    int main()
    {
    	memset(f,0x3f,sizeof f);
    	f[1][0]=0;
    	for(int i=1;i<(1<<n);i+=2) 
    	{
    		for(int j=0;j<n;j++)
    		{
    			if(!((i >> j) & 1)) continue; //当前i状态下,根本没有走过j
    			for(int k=0;k<n;k++)
    			{
    				if(j==k) continue;
    				if(!(i>>k & 1)) continue; //上一次状态,根本没有走过k,就更不可能从k转移到j
    				f[i][j]=min(f[i][j],f[i^(1<<j)][k]+weight[k][j]);
    			}
    		}
    	}
    	cout << f[(1<<n)-1][n-1]; // 所有点走完,最后停在n-1点上
    }
    

    5.优先级

    首先,建议在不清楚优先级的情况下加括号
    其次,大小关系比较要优于&,|,^, 加括号
    优先顺序表,从上到下,依次降低

    运算 运算符
    加减 +、-
    移位 <<、>>
    比较大小 >、<、==、!=
    位与 &
    异或 ^
    位或
  • 相关阅读:
    HDU Railroad (记忆化)
    HDU 1227 Fast Food
    HDU 3008 Warcraft
    asp vbscript 检测客户端浏览器和操作系统(也可以易于升级到ASP.NET)
    Csharp 讀取大文本文件數據到DataTable中,大批量插入到數據庫中
    csharp 在万年历中计算显示农历日子出错
    csharp create ICS file extension
    CSS DIV Shadow
    DataTable search keyword
    User select fontface/color/size/backgroundColor设置 字体,颜色,大小,背景色兼容主流浏览器
  • 原文地址:https://www.cnblogs.com/p-z-y/p/11198657.html
Copyright © 2011-2022 走看看