导航

位运算简介及实用技巧(四):实战篇

Posted on 2019-05-20 16:05  ccjvl  阅读(462)  评论(0编辑  收藏  举报

原文:http://www.matrix67.com/blog/archives/268

    下面分享的是我自己写的三个代码,里面有些题目也是我自己出的。这些代码都是在我的Pascal时代写的,恕不提供C语言了。代码写得并不好,我只是想告诉大家位运算在实战中的应用,包括了搜索和状态压缩DP方面的题目。其实大家可以在网上找到更多用位运算优化的题目,这里整理出一些自己写的代码,只是为了原创系列文章的完整性。这一系列文章到这里就结束了,希望大家能有所收获。
    Matrix67原创,转贴请注明出处。

Problem : 费解的开关

题目来源
    06年NOIp模拟赛(一) by Matrix67 第四题

问题描述
    你玩过“拉灯”游戏吗?25盏灯排成一个5×5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
    我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。下面这种状态

10111
01101
10111
10000
11011

    在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

    再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

    给定一些游戏的初始状态,编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。

输入格式
    第一行有一个正整数n,代表数据中共有n个待解决的游戏初始状态。
    以下若干行数据分为n组,每组数据有5行,每行5个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。
    对于30%的数据,n<=5;
    对于100%的数据,n<=500。

输出格式
    输出数据一共有n行,每行有一个小于等于6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。
    对于某一个游戏初始状态,若6步以内无法使所有灯变亮,请输出“-1”。

样例输入
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

样例输出
3
2
-1

程序代码
const
   BigPrime=3214567;
   MaxStep=6;
type
   pointer=^rec;
   rec=record
         v:longint;
         step:integer;
         next:pointer;
       end;

var
   total:longint;
   hash:array[0..BigPrime-1]of pointer;
   q:array[1..400000]of rec;

function update(a:longint;p:integer):longint;
begin
   a:=a xor (1 shl p);
   if p mod 5<>0 then a:=a xor (1 shl (p-1));
   if (p+1) mod 5<>0 then a:=a xor (1 shl (p+1));
   if p<20 then a:=a xor (1 shl (p+5));
   if p>4 then a:=a xor (1 shl (p-5));
   exit(a);
end;

function find(a:longint;step:integer):boolean;
var
   now:pointer;
begin
   now:=hash[a mod BigPrime];
   while now<>nil do
   begin
      if now^.v=a then exit(true);
      now:=now^.next;
   end;

   new(now);
   now^.v:=a;
   now^.step:=step;
   now^.next:=hash[a mod BigPrime];
   hash[a mod BigPrime]:=now;
   total:=total+1;
   exit(false);
end;

procedure solve;
var
   p:integer;
   close:longint=0;
   open:longint=1;
begin
   find(1 shl 25-1,0);
   q[1].v:=1 shl 25-1;
   q[1].step:=0;
   repeat
      inc(close);
      for p:=0 to 24 do
         if not find(update(q[close].v,p),q[close].step+1) and (q[close].step+1<MaxStep) then
         begin
            open:=open+1;
            q[open].v:=update(q[close].v,p);
            q[open].step:=q[close].step+1;
         end;
   until close>=open;
end;

procedure print(a:longint);
var
   now:pointer;
begin
   now:=hash[a mod BigPrime];
   while now<>nil do
   begin
      if now^.v=a then
      begin
         writeln(now^.step);
         exit;
      end;
      now:=now^.next;
   end;
   writeln(-1);
end;

procedure main;
var
   ch:char;
   i,j,n:integer;
   t:longint;
begin
   readln(n);
   for i:=1 to n do
   begin
      t:=0;
      for j:=1 to 25 do
      begin
         read(ch);
         t:=t*2+ord(ch)-48;
         if j mod 5=0 then readln;
      end;
      print(t);
      if i<n then readln;
   end;
end;

begin
   solve;
   main;
end.

=======================  性感的分割线  =======================

Problem : garden / 和MM逛花园

题目来源
    07年Matrix67生日邀请赛第四题

问题描述
    花园设计强调,简单就是美。Matrix67常去的花园有着非常简单的布局:花园的所有景点的位置都是“对齐”了的,这些景点可以看作是平面坐标上的格点。相邻的景点之间有小路相连,这些小路全部平行于坐标轴。景点和小路组成了一个“不完整的网格”。
    一个典型的花园布局如左图所示。花园布局在6行4列的网格上,花园的16个景点的位置用红色标注在了图中。黑色线条表示景点间的小路,其余灰色部分实际并不存在。
        

    Matrix67 的生日那天,他要带着他的MM在花园里游玩。Matrix67不会带MM两次经过同一个景点,因此每个景点最多被游览一次。他和他MM边走边聊,他们是 如此的投入以致于他们从不会“主动地拐弯”。也就是说,除非前方已没有景点或是前方的景点已经访问过,否则他们会一直往前走下去。当前方景点不存在或已游 览过时,Matrix67会带MM另选一个方向继续前进。由于景点个数有限,访问过的景点将越来越多,迟早会出现不能再走的情况(即四个方向上的相邻景点 都访问过了),此时他们将结束花园的游览。Matrix67希望知道以这种方式游览花园是否有可能遍历所有的景点。Matrix67可以选择从任意一个景 点开始游览,以任意一个景点结束。
  在上图所示的花园布局中,一种可能的游览方式如右图所示。这种浏览方式从(1,2)出发,以(2,4)结束,经过每个景点恰好一次。
  输入格式
  第一行输入两个用空格隔开的正整数m和n,表示花园被布局在m行n列的网格上。
  以下m行每行n个字符,字符“0”表示该位置没有景点,字符“1”表示对应位置有景点。这些数字之间没有空格。
  输出格式
  你的程序需要寻找满足“不主动拐弯”性质且遍历所有景点的游览路线。
  如果没有这样的游览路线,请输出一行“Impossible”(不带引号,注意大小写)。
  如果存在游览路线,请依次输出你的方案中访问的景点的坐标,每行输出一个。坐标的表示格式为“(x,y)”,代表第x行第y列。
  如果有多种方案,你只需要输出其中一种即可。评测系统可以判断你的方案的正确性。
  样例输入
  6 4
  1100
  1001
  1111
  1100
  1110
  1110
  样例输出
  (1,2)
  (1,1)
  (2,1)
  (3,1)
  (4,1)
  (5,1)
  (6,1)
  (6,2)
  (6,3)
  (5,3)
  (5,2)
  (4,2)
  (3,2)
  (3,3)
  (3,4)
  (2,4)
  数据规模
  对于30%的数据,n,m<=5;
  对于100%的数据,n,m<=10。 
  </BLOCKQUOTE>
  程序代码:
  <CODE>program garden;
  const
  dir:array[1..4,1..2]of integer=
  ((1,0),(0,1),(-1,0),(0,-1));
  type
  arr=array[1..10]of integer;
  rec=record x,y:integer;end;
  var
  map:array[0..11,0..11]of boolean;
  ans:array[1..100]of rec;
  n,m,max:integer;
  step:integer=1;
  state:arr;
  procedure readp;
  var
  i,j:integer;
  ch:char;
  begin
  readln(m,n);
  for i:=1 to n do
  begin
  for j:=1 to m do
  begin
  read(ch);
  map[i,j]:=(ch='1');
  inc(max,ord( map[i,j] ))
  end;
  readln;
  end;
  end;
  procedure writep;
  var
  i:integer;
  begin
  for i:=1 to step do
  writeln( '(' , ans.x , ',' , ans.y , ')' );
  end;
  procedure solve(x,y:integer);
  var
  tx,ty,d:integer;
  step_cache:integer;
  state_cache:arr;
  begin
  step_cache:=step;
  state_cache:=state;
  if step=max then
  begin
  writep;
  exit;
  end;
  for d:=1 to 4 do
  begin
  tx:=x+dir[d,1];
  ty:=y+dir[d,2];
  while map[tx,ty] and ( not state[tx] and(1 shl (ty-1) )>0) do
  begin
  inc(step);
  ans[step].x:=tx;
  ans[step].y:=ty;
  state[tx]:=state[tx] or ( 1 shl (ty-1) );
  tx:=tx+dir[d,1];
  ty:=ty+dir[d,2];
  end;
  tx:=tx-dir[d,1];
  ty:=ty-dir[d,2];
  if (tx<>x) or (ty<>y) then solve(tx,ty);
  state:=state_cache;
  step:=step_cache;
  end;
  end;
  {====main====}
  var
  i,j:integer;
  begin
  assign(input,'garden.in');
  reset(input);
  assign(output,'garden.out');
  rewrite(output);
  readp;
  for i:=1 to n do
  for j:=1 to m do
  if map[i,j] then
  begin
  ans[1].x:=i;
  ans[1].y:=j;
  state:=1 shl (j-1);
  solve(i,j);
  state:=0;
  end;
  close(input);
  close(output);
  end.</CODE>
 

  对C语言中位运算的一点补充:(位数不同的运算数之间的运算规则)-
  C语言中,位运算的对象可以是整型(int)和字符型(char)数据。(整形数据可以直接转化成二进制数,字符型数据在内存中以它的ASCII码值存放,也可以站化成二进制数)当两个运算数类型不同时,位数亦会不同。遇到这种情况,系统将自动进行如下处理:
  1将两个运算数右端对齐。
  2 再将位数短的一个运算数往高位扩充,即:无符号数和正整数左侧用0补全;负数左侧用1补全;然后对位数相等的两个运算数,按位进行运算。