【搜索】【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.