费解的开关(vijos1197)

本文介绍了一种解决复杂开关谜题的有效算法。该算法利用广度优先搜索(BFS)及状态压缩技巧,通过反向搜索从目标状态回溯可能的起始状态。文章详细解释了如何运用位运算提高搜索效率,并提供了具体的程序实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法:搜索

很经典的一道题,也是很考验选手水平的一道题。

本题总共有两种方法:

费解的开关
    题目本身是一个经典的广搜问题,题目读完后可以立即写出一个广搜的程序。但是,这只能通过30%的小数据;当n=500时做500次普通的广搜必然超时。或许,有人曾想过提高每一次广搜的效率,比如采用双向搜索。连双向搜索都已经想到了,为何不尝试一下反向搜索呢?
    程序读入数据前我们需要一次广搜作为初始化。我们可以从最终状态出发,用一次广搜倒推出所有前6步的状态。这样,所有能够在6步以内完成的状态我们都求出来了。以后读入数据,问到哪个答哪个,如果读入的状态不在我们求出的状态中直接输出“-1”就行了。
    在程序实现时,我们可以把状态压缩成25位的二进制数并用十进制储存。使用位运算将使得程序运行得更快。xor有一个很好的特性,一个Boolean值与True进行xor运算后将取反,与False进行xor运算后值不变。于是,对二进制数a的右数第4个数字取反,我们可以用a xor (1 shl 3)来完成。你完全可以把25种状态变化所对应的要xor的值先算出来。
    有一个小地方值得注意:第三个数据出现了输出为0的情况,即某个初始状态全部为1。很多人在这里没有处理好。

补充
    疾风剑客提供了一个相当快的算法,它的算法能在相当短的时间里算出给定状态到最终状态所需要的步数:对于每个状态,算法只需要枚举第一行改变哪些灯的状态,只要第一行的状态固定了,接下来的状态改变方法都是唯一的:每一行需要改变状态的位置都在上一行中不亮的灯的正下面,因为只有这样才能使上一行的灯全亮。我们枚举第一行的状态改变方法(共2^5种),对于每种方法都依次改变下面几行的状态使上面一行灯全亮。到最后一行我们需要判断是否最后一行也恰好全亮,并更新最小步数。好像这个算法能在0ms内出结果。
 
这题一开始的BFS我想到了,但是好像非常裸,直接就TLE了,后来看了某神的题解才知道既需要状态压缩还需要反向搜索。(PS:状态压缩想到了,反向没想到。)    
对于存储状态我弄得是个很裸的线性表,某神用的强HASH,让我情何以堪啊~这说明自己对HASH的理解还不够深,主要是对于HASH的理解还很弱。

另外对于本题的改变开关的操作使用位运算,xor是一种很好的运算。(和1做xor,都相同为0,不相同为1,其实用not也可以)

program vijos1197;

const
 maxn=25;
 maxzt=3000011;
 dx:array [1..4] of -1..1=(0,-1,0,1);
 dy:array [1..4] of -1..1=(-1,0,1,0);
 
type
 atp=record
  zt,dep:longint;
 end;

var
 start,n,ii,head,tail:longint;
 que,dep,h:array [0..maxzt] of longint;{h为HASH数组,que存储状态,dep存储搜索深度。}
 b:array [0..maxn] of longint;{b用来表示操作。}
 
function hash(x:longint):longint;{一种高效的用于存储状态的方法—HASH}
var
 tmp,i:longint;
begin
 tmp:=x mod maxzt;{HASH初始化。}
 i:=tmp;
 while (h[i]<>0) and (h[i]<>x) do{这里是确定状态的部分。}
  begin
   inc(i);
   if i>=maxzt then i:=i mod maxzt;   
  end;
 if h[i]=0 then h[i]:=x;
 exit(i);
end;
 
procedure predo;
var
 i,j,k,t,tx,ty:longint;
begin
 head:=0;
 tail:=1;
 que[1]:=(1 shl 25)-1;
 dep[hash(que[1])]:=1;
 {生成转换的方式。}
 for i:=1 to 5 do 
  begin
   for j:=1 to 5 do
    begin
     t:=(i-1)*5+j;
     b[t]:=b[t]+1 shl (25-t);
     {这里操作之后,上下左右四个方向都需要改变。}
     for k:=1 to 4 do 
      begin
       tx:=i+dx[k];
       ty:=j+dy[k];
       if (tx in [1..5]) and (ty in [1..5]) then b[t]:=b[t]+1 shl (25-(tx-1)*5-ty);      
      end;
    end;
  end;
end;

procedure make;
var
 x,i,xx:longint;
begin
 while head<tail do 
  begin
   inc(head);
   x:=que[head];
   if dep[hash(x)]<=6 then
    begin
     for i:=1 to 25 do
      begin
       xx:=x xor b[i];{25种转换方式。}
       if dep[hash(xx)]=0 then
        begin
									dep[hash(xx)]:=dep[hash(x)]+1;
									inc(tail);
									que[tail]:=xx;
        end;
      end;
    end;
  end;
end;
 
procedure init;
var
 i:longint;
 ch:char;
begin
 start:=0;
 for i:=1 to 25 do
  begin
   read(ch);
   start:=start+(ord(ch)-48) shl (25-i);
   if i mod 5=0 then readln;
  end;
end;

begin
 assign(input,'VJ1197.in'); reset(input);
 assign(output,'VJ1197.out'); rewrite(output);
 
 readln(n);
 predo;
 make;
 for ii:=1 to n do 
  begin
   init;
   if dep[hash(start)]=0 then writeln(-1) else writeln(dep[hash(start)]-1);{直接判断。}
   readln;
  end;
 
 close(input); close(output);
end.




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值