问题
农民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.