luogu 1074靶形数独-位运算优化

12.1 位运算符和位运算

运算符  含义

&    按位与

|    按位或

^    按位异或

~    取反

<<   左移

>>   右移

说明:

(1)位运算符中除 ~ 外,均为二目运算符,即要求出侧各有一个运算量。

(2)运算早只能是整型或字符型的数据,不能为实型数据。

12.1.1 按位与运算符 &

  参加运算的两个数制,按二进制进行 与运算。如果两个相应的二进位数为1,刚该位的结果为 1 否则为 0 即:

  0 & 0 = 0;0 & 1 = 0;1 & 0 = 0;1& 1 = 1

例如:3 & 8 并不等于8,应该是按位与

3 = 00000011

5 = 00000101 &

      00000001

  因此 3 & 5 的值得 1, 如果参加 & 是负数(-3 & -5),则以补码形式表示为二进制数。然后按位进行 与 运算。

  按拉与有一些特殊的用途:

  (1)清零。如果想将一个单元清零,即使其全部二进位为 0,只要找一个二进制数,其中各个位符合以下条件:原来数中为 1 的位,新数中相应位为 0。然后使二者进行 & 运算,即可以达到清零目的。

  (2)取一个数中某些指定位。如有一个整数 a (2个字节)想要其中的低字节。只需将 a 与(337)。按位与即可。

  (3)要想将哪一个保留下来,就与一个数进行 & 运算,此数在该位位1,如有一个数 01010100,想把其中左面第3,4,5,7,8可以这样运算:

01010100

00111011 &

00010000

12.1.2 按位或运算符 |
  两个相应的二进位中只要有一个为 1,该位的结果就为 1。

0|0=0; 0|1=1; 1|0=1; 1|1=1;

  按位或运算常用来对一个数据的某些位定值为1,如 a 是一个整数(16位)有表达式 a & 0377,则低 8 位全置为 1。高 8 位保留原样。

12.1.3 异或运算符 ^

  异或运算符 ^ 也称 XOR 运算符。它的规则是若参加运算的两个二进位同号,则结果为0,异号则为1。即 0^0=0; 0^1=1; 1^0=1;1^1=0;

下面举例说明 ^ 运算符的应用。

  (1)使特定位翻转

  假设有 01111010,想使其低4 位翻转,即 1 变为 0,0 变为 1,可以将它与 00001111进行 ^ 运算,即

01111010

00001111 ^

01110101

结果值的低 4 位正好是原数低4位的翻转。

  (2)与 0 相 ^ 保留原值

如 012 ^ 00 = 012

00001010

00000000 ^

00001010

因为原数中的 1 与 0 进行 ^ 运算得 1,0 与 1 运算得 0,故保留原数。

  (3)交换两个值,不用临时变量

  假如 a = 3, b = 4。想将 a 和 b 的值互换,可以用以下赋值语句实现:

  a = a ^ b;

  b = b ^ a;

  a = a ^ b;

a = 011

b = 100 //a = a ^ b;

a = 111//a = 7

b = 100 //b = b ^ a;

b = 011// b = 3

a = 111//a = a ^ b;

a = 100 // 4

12.1.4 取反运算符 ~

  ~是一个头单目运算符,用来对一个二进制按位取反,即将 0 变 1,1变 0。例如~25 是对八进制数 25 (即 00010101)按位取反。

00000000 00010101

11111111 11101010 ~

  ~运算符的优先级别比算术运算符,关系运算符,逻辑运算符和其它运算符都高,例如:~a & b,先进行 ~a 然后进行 & 运算。

12.1.5 左移运算符 <<

  用来将一个数各二进位全部左移若干位。例如:

  a = a << 2;

将 a 的二进制数左移 2 位,右补 0,若 a = 15,即二进制数 00001111,左移2位得到 00111100,即十进制数60.

  高位左移后溢出,舍弃不起作用。

  左移一位相当于该数乘以2。但些结论只适用于该数左移时被溢出舍弃的高位中不包含1 的情况。

  左移比乘法运算快得多,有些C编译程序自动将乘2的运算用左移来实现。

12.1.6 右移运算符 >>

  a >> 2 表示将 a 的各二进位右移 2 位。移到右端的低位被舍弃,对无符号数,高位补 0。如 a = 017 时:

a = 00001111 >> 2

00000011

  右移一位相当于除以 2 ,右移 n 位相当于除于 2^n。

  在右移时,需要注意符号位问题。对无符号数,右移时左边高位移入 0。对于有符号的值,如果原来符号位为 0 (该数为正),则左边也是移入 0,如果上例表示的那样,如果符号位原来为 1(该数为负),则左边移入的 0 还是 1 ,要取决于所用的计算机系统。移入 0 称为 逻辑右移,即简单右移。移入 1 称为 算术右移。

12.1.7 位运算赋值运算符

  位运算符与赋值运算符可以组成复合赋值运算符。

  如:&=, |=, >>|, <<=, ^=

12.1.8 不同长度的数据进行位运算

  如果两个数据长度不同(例如 long 型和 int 型)进行位运算时(如 a & b 而 a 为 long型,b 为 int 型),系统会将二者按右端对齐。如果 b 为正数,则左侧 16 位补满 0。若 b 为负数,左端应补满 1。如果 b 为无符号整数型,则左侧补满 0。

12.2 位运算举例

  取一个整数 a 从右端开始的 4~7 位

  (1)先使 a 右移 4 位

  a >> 4;

  (2)设置一个低 4 位全为 1 ,其余全为 0 的数,可以用下面方法实现:

  ~(~0<<4)

~0 的全部二进制为 1 ,左移 4 位,这样右端低 4 位为 0。

  (3)将上面二者进行 & 运算。

  a >> 4 & ~(~0<<4)

#include <stdio.h>

void main()

{

  unsigned a, b, c, d;

  scanf("%o", &a);

  b = a >> 4;

  c = ~(~0<<4);

  d = b & c;
}

循环移位

#include <stdio.h>

void main()

{

  unsigned a, b, c;

  int n;

  scanf("a=%o, n=%d", &a, &b);

  b = a << (16 - n);

  c = a >> n;

  c = c | b;

  printf("%o\n%o", a, c);
}

位运算应用:数位压缩dp,优化搜索等。

靶形数独

#include<stdio.h>
#include<math.h>
int h[10]= {},hs[10]= {},zs[10]= {},xj[5][5]= {},hq[10]= {}; //全都清零一下,具体意思下面解释。
int ans=-1,st[10],a[10][10];
void make() { //这个是算分值,更新ans
	int sum=0,i,j;
	for (i=1; i<5; i++) {
		for (j=i; j<11-i; j++)
			sum+=(a[i][j]+a[10-i][j])*(5+i);
		for (j=i+1; j<10-i; j++)
			sum+=(a[j][i]+a[j][10-i])*(5+i);
	}
	sum+=a[5][5]*10;
	if (sum>ans) ans=sum;
}
void dfs(int k) { //搜索部分,k不表示搜索第k行,而是第st[k]行
	if (k==10) make(); // k=10表示九行都搞定了,开始算分
	else {
		int x,y,j,pos,p,i=k; //i表示第i行,j表示第j列
		x=511-h[i];//511(10)=111,111,111(2),故此行的意思是将第i行缺位取出来此时x中1表示缺位,y与x同
		y=x&-x; //lowbit,找出第一个1的位置。y是取出本行第一个缺位,在这一次搜索里就搜索这个缺位
		h[i]|=y;//下一次搜索时,这一位已填,故把缺位补上
		j=(int)log2(y)+1; //j就是y用二进制表示1所在的位数,即j列
		pos=511-(hs[i]|zs[j]|xj[(i-1)/3][(j-1)/3]); //这一步是取出可以填哪些数
		while (pos>0) {
			p=pos&-pos;  //取出可以填的一个数
			pos-=p;       //去掉已填的数
			a[i][j]=(int)log2(p)+1; //填入a中
			hs[i]|=p;    //修改hs,zs,xj,这个数已用过,‘或’写成‘+’也行
			zs[j]|=p;
			xj[(i-1)/3][(j-1)/3]|=p;
			if (x==y) dfs(k+1);//若x=y,则这一行只有一个空,即现在已填的空,故搜索k+1
			else dfs(k); //若x<>y,则这一行还有空没填,继续搜索这一行
			hs[i]-=p; //搜索完,还原hs,zs,xj
			zs[j]-=p;
			xj[(i-1)/3][(j-1)/3]-=p;
		};
		h[i]-=y;  //s搜索完,还原h[i]
	};
}
int main() {
	int i,j,p0;
	for (i=1; i<10; i++)
		for (j=1; j<10; j++) {
			scanf("%d",&a[i][j]); //读入数独,数组a记的是数独。
			if (a[i][j]>0) {
				h[i]|=1<<(j-1);  //数组h记的是某一行填数情况
				//h[i]写成二进制,第j位为0,表示a[i][j]=0,即没填同理第j位为1,表示a[i][j]>0,已填数
				p0=1<<(a[i][j]-1); //p0写成二进制,第k位为1,表示数字k已用过
				if (((hs[i]&p0)!=0)||((zs[j]&p0)!=0)||
				        ((xj[(i-1)/3][(j-1)/3]&p0)!=0)) {
					printf("-1\n");
					return 0;
				};  //这个判断是看数独有没有错,即某一行(列,九宫格)是否有同一数字出现两次
				hs[i]|=p0;  //数组hs记的是某一行数字用的情况
				//hs[i]写成二进制,第j位为0,表示i行,j没用过,同理第j位为1,表示i行,j用过

				zs[j]|=p0;    //数组zs记的是某一列(纵行)数字用的情况,意义同hs

				xj[(i-1)/3][(j-1)/3]|=p0; //数组xj记的是某一小九宫格数字用的情况,意义同hs
			}        //九个小九宫格分别是     xj[0][0] xj[0][1] xj[0][2]
			//xj[1][0] xj[1][1] xj[1][2]
			//xj[2][0] xj[2][1] xj[2][2]
			else hq[i]++;
		}  //数组hq记的是某一行缺数的个数,唯一的优化的组成部分。不加这个优化洛谷上有4个点不过。
   for (i=1; i<10; i++) st[i]=i;       //数组st记的是搜索各行的顺序,就是先搜哪一行,再搜哪一行
		for (i=1; i<9; i++)               //此部分是按各行空缺数的个数将st从小到大排序
		for (j=i+1; j<10; j++)          //使得一会搜的时候,先搜缺数少的行,这也就是唯一的优化
			if (hq[st[i]]>hq[st[j]]) {
				st[i]^=st[j];               //交换两数位运算版
				st[j]^=st[i];
				st[i]^=st[j];
			}

	for (i=1; hq[st[i]]==0; i++);       //考虑到某一行缺数可能为0,故先找到缺数的行
	
	dfs(1);                          //开始搜索
	printf("%d\n",ans);               //ans就是答案
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值