【搜索】【usaco 4.1.4】奶牛加密术

问题

农民Brown和John的牛们计划协同逃出它们各自的农场。它们设计了一种加密方法用来保护它们的通讯不被他人知道。

如果一头牛有信息要加密,比如"International Olympiad in Informatics",它会随机地把C,O,W三个字母插到到信息中(其中C在O前面,O在W前面),然后它把C与O之间的文字和 O与W之间的文字的位置换过来。这里是两个例子:

International Olympiad in Informatics
-> 
CnOIWternational Olympiad in Informatics
International Olympiad in Informatics
-> 
International Cin InformaticsOOlympiad W

为了使解密更复杂,牛们会在一条消息里多次采用这个加密方法(把上次加密的结果再进行加密)。一天夜里,John的牛们收到了一条经过多次加密的信息。请你写一个程序判断它是不是这条信息经过加密(或没有加密)而得到的:

Begin the Escape execution at the Break of Dawn

[编辑]格式

PROGRAM NAME:cryptcow

INPUT FORMAT:

(file cryptcow.in)

一行,不超过75个字符的加密过的信息。

OUTPUT FORMAT:

(file cryptcow.out)

一行,两个整数. 如果能解密成上面那条逃跑的信息,第一个整数应当为1,否则为0;如果第一个数为1,则第二个数表示此信息被加密的次数,否则第二个数为0。

[编辑]SAMPLE INPUT

Begin the EscCution at the BreOape execWak of Dawn

[编辑]SAMPLE OUTPUT

1 1

 

分析

我们先明白它变换的法则,由加密后的串变回原串,只需要做原操作的逆运算即可,也就是再按题目要求对串进行操作。但是我们并不知道给的串中哪些cow

是一对,所以我们要枚举情况,然后深度优先搜索,变换一次就压栈。

在这里插一句,当年自己竟然打不出傻搜的代码,这里给个深搜的框架

dfs(当前的状态描述)

begin

if 达到边界(已经产生可行性的解)then

begin

  如果该解满足最优性 then 更新最优解

  exit

end;

可行性剪枝

最优性剪枝

……

if 当前状态满足可行性(当前状态未被搜索过)then

  递归搜索下一状态

end;

然后就是对于这道题的剪枝了:

1、由于添加的COW是一起的,因此给出的字符串的字符个数应该等于47(目标字符串的长度)+3*k。如果不满足就可直接判断无解。
2、除了COW三个字符外,其他的字符的个数应该和目标串相一致。如果不一致也可直接判断无解。
3、搜索中间肯定会出现很多相同的情况,因此需要开一个hash来记录搜索到过哪些字符串,每搜索到一个字符串,就判重。如果重复直接剪枝。这里的字符串的hash函数可以采用ELFhash,但由于ELFhash的数值太大,所以用函数值对一个大质数(我用的是99991)取余,这样可以避免hash开得太大,同时又可以减少冲突。
4、对搜索到的字符串,设不包含COW的最长前缀为n前缀(同样也可以定义n后缀),那么如果n前缀不等于目标串的长度相同的前缀,那么当前字符串一定无解,剪枝。N后缀也可采取相同的判断方法。
5、一个有解的字符串中,COW三个字母最早出现的应该是C,最后出现的应该是W,如果不满足则剪枝。
6、当前字符串中任意两个相邻的COW字母中间所夹的字符串一定在目标串中出现过。如果不符合可立即剪枝。
7、需要优化搜索顺序。经过试验我们可以发现,O的位置对于整个COW至关重要。可以说,O的位置决定了整个串是否会有解。因此,我们在搜索时,应该先枚举O的位置,然后再枚举C和W的位置。其中W要倒序枚举。这样比依次枚举COW至少要快20~30倍。
8、在判断当前串的子串是否包含在目标串中的时候,可以先做一个预处理:记录每一个字母曾经出现过的位置,然后可以直接枚举子串的第一个字母的位置。这样比用pos要快2倍左右。

经过上述优化,程序对于极端数据也可以在1s以内出解。

其实只用3、4、6、7就可以进1s。

事实证明优化6相当重要!对于字符串哈希,elf显得很慢,代码中给出的哈希函数效率在相比之下要高的多。

反思

搜索的关键是找到合适的状态描述,代码一般很简单。剪枝的话从可行性和最优性考虑,最起码把很显然的剪枝加上,不要担心对剪枝条件的考察会很大程度上影响搜索的时间,这道题很考验人,也提供了很好的剪枝思路。

code

program liukee;
var
  hash:array[0..1000000] of boolean;
  goal,s:string;
procedure can;
begin
  writeln(1,' ',(length(s)-length(goal))div 3);
  close(input);
  close(output);
  halt;
end;

procedure cannot;
begin
  writeln('0 0');
  close(input);
  close(output);
  halt;
end;

function addhash(st:string):boolean;
  var i:integer;
      value:longint;
  begin
    value:=0;
    for i:=1 to length(st) do
      value:=(value*131+ord(st[i])) mod 97011;
    if (hash[value]) then exit(true);
    hash[value]:=true;
    exit(false);
  end;

function check(s1:string):boolean;
var
  temp,j,k,i:longint;
begin
  if length(s1)<=47 then exit(false);
  if addhash(s1) then exit(false);
  j:=47;
  k:=length(s1);
  while goal[j]=s1[k] do
  begin
    dec(j);
	dec(k);
  end;
  if s1[k]<>'W' then exit(false);
  k:=1;
  while goal[k]=s1[k] do inc(k);
  if s1[k]<>'C' then exit(false);
  for i:=k+1 to length(s1)do
    if (s1[i] in ['C','O','W']) then
	  if i>k+1 then
	    if pos(copy(s1,k+1,i-1-k),goal)=0 then exit(false) else k:=i
		else
		k:=i;
	exit(true);
end;

procedure dfs(s:string);
var
  i,j,k:integer;
begin
  if s=goal then can;
  if check(s) then
    for i:=2 to length(s)-1 do
      if s[i]='O' then
        for j:=1 to i-1 do
          if s[j]='C' then
            for k:=length(s) downto i+1 do
              if s[k]='W' then
    dfs(copy(s,1,j-1)+copy(s,i+1,k-i-1)+copy(s,j+1,i-j-1)+copy(s,k+1,length(s)-k));
end;
				
begin
  assign(input,'cryptcow.in');
  assign(output,'cryptcow.out');
  reset(input);
  rewrite(output);
  fillchar(hash,sizeof(hash),0);
  goal:='Begin the Escape execution at the Break of Dawn';
  readln(s);
  if length(s)<47 then cannot;
  if (length(s)-47) mod 3<>0 then cannot;
  dfs(s);
  cannot;
end.


posted @ 2010-11-16 14:09  liukee  阅读(661)  评论(0编辑  收藏  举报