zoukankan      html  css  js  c++  java
  • 从Hash Killer I、II、III论字符串哈希

    首先,Hash Killer I、II、III是BZOJ上面三道很经典的字符串哈希破解题。当时关于II,本人还琢磨了好久,但一直不明白为啥别人AC的代码都才0.3kb左右,直到CYG神犇说可以直接随机水过去,遂恍然大悟。。。

    于是,本人今天也做了下实验——假设现在有一个字符串题:输入N,接下来N行输入N个长度一样的由大写字母组成的字符串,求一共有多少种不同的字符串。此题有些类似于Hash Killer上面的原题。首先分析此题本身,两种常规办法:1.建立一棵字典树,然后可以相当方便快捷的判重,对于字符串长度均为M的数据,复杂度O(NM)  2.字符串哈希,选取一对质数pa和pb,哈希值为Sigma((ord(s1[i])-64)*pa^i) mod pb,然后通过哈希值排个序完事

    接下来开始——字典树肯定能保证正确这个毫无疑问,但是更加毫无疑问的是哈希是相当容易被卡掉的(HansBug:尤其像Hash Killer II这样素数的神选取我也是醉了),但更加更加毫无疑问的是双取模哈希似乎还比较小强,于是我就此展开实验

    1.写出一个数据生成器,负责随机生成N个长度为M的大写字母字符串,然后立刻用Trie树求出答案作为标准输出数据

     1 type
     2         point=^node;
     3         node=record
     4                 ex:longint;
     5                 next:array['A'..'Z'] of point;
     6         end;
     7 var
     8         i,j,k,l,m,n,ans:longint;
     9         head:point;
    10         s1,s2:ansistring;
    11 function getpoint:point;inline;
    12         var p:point;c1:char;
    13         begin
    14                 new(p);p^.ex:=0;
    15                 for c1:='A' to 'Z' do p^.next[c1]:=nil;
    16                 exit(p);
    17         end;
    18 function check(s1:ansistring):longint;inline;
    19         var i:longint;p:point;
    20         begin
    21                 p:=head;
    22                 for i:=1 to length(s1) do
    23                         begin
    24                                 if p^.next[s1[i]]=nil then
    25                                         p^.next[s1[i]]:=getpoint;
    26                                 p:=p^.next[s1[i]];
    27                         end;
    28                 if p^.ex=0 then
    29                         begin
    30                                 inc(ans);
    31                                 p^.ex:=ans;
    32                         end;
    33                 exit(p^.ex);
    34         end;
    35 begin
    36         readln(n,m);
    37         head:=getpoint;ans:=0;
    38         RANDOMIZE;
    39         assign(output,'hs.in');
    40         rewrite(output);
    41         writeln(n);
    42         for i:=1 to n do
    43                 begin
    44                         s1:='';
    45                         for j:=1 to m do s1:=s1+chr(random(26)+65);
    46                         writeln(s1);
    47                         check(s1);
    48                 end;
    49         close(output);
    50         assign(output,'hss.out');
    51         rewrite(output);
    52         writeln(ans);
    53         close(output);
    54 end.

    2.接下来,开始写哈希,也不难,而且代码貌似还略短(这里面两个素数采用互换使用的模式,本程序是双取模的哈希,如果需要改成单值哈希的话直接把第50行去掉即可)

     1 const pa=314159;pb=951413;
     2 var
     3    i,j,k,l,m,n:longint;
     4    ap,bp:array[0..100000] of int64;
     5    a:array[0..200000,1..2] of int64;
     6    a1,a2,a3,a4:int64;
     7    s1,s2:ansistring;
     8 function fc(a1,a2,a3,a4:int64):longint;inline;
     9          begin
    10               if a1<>a3 then
    11                  if a1>a3 then exit(1) else exit(-1)
    12               else
    13                   if a2<>a4 then
    14                      if a2>a4 then exit(1) else exit(-1)
    15                   else exit(0);
    16          end;
    17 procedure sort(l,r:longint);
    18           var i,j:longint;x,y,z:int64;
    19           begin
    20                i:=l;j:=r;x:=a[(l+r) div 2,1];y:=a[(l+r) div 2,2];
    21                repeat
    22                      while fc(a[i,1],a[i,2],x,y)=-1 do inc(i);
    23                      while fc(x,y,a[J,1],a[J,2])=-1 do dec(j);
    24                      if i<=j then
    25                         begin
    26                              z:=a[i,1];a[i,1]:=a[j,1];a[j,1]:=z;
    27                              z:=a[i,2];a[i,2]:=a[j,2];a[j,2]:=z;
    28                              inc(i);dec(j);
    29                         end;
    30                until i>j;
    31                if i<r then sort(i,r);
    32                if l<j then sort(l,j);
    33           end;
    34 
    35 begin
    36      ap[0]:=1;bp[0]:=1;
    37      for i:=1 to 100000 do
    38          begin
    39               ap[i]:=(ap[i-1]*pa) mod pb;
    40               bp[i]:=(bp[i-1]*pb) mod pa;
    41          end;
    42      readln(n);
    43      for i:=1 to n do
    44          begin
    45               readln(s1);
    46               a[i,1]:=0;a[i,2]:=0;
    47               for j:=1 to length(s1) do
    48                   begin
    49                        a[i,1]:=(a[i,1]+ap[j]*(ord(s1[j])-64)) mod pb;
    50                        a[i,2]:=(a[i,2]+bp[j]*(ord(s1[j])-64)) mod pa;  //删除此行即可改为单值哈希
    51                   end;
    52          end;
    53      sort(1,n);m:=0;
    54      a[0,1]:=-maxlongint;
    55      for i:=1 to n do if fc(a[i-1,1],a[i-1,2],a[i,1],a[i,2])<>0 then inc(m);
    56      writeln(m);
    57      readln;
    58 end.
    59                       

    于是开始愉快的用bat来对拍:

    1.当N=100000 M=3时,很令人吃惊——单双值的哈希都问题不大(随机跑了403组数据均全部通过)

    2.当N=100000 M=100是,果不其然——单值的哈希成功而华丽的实现了0%的命中率,而双值的哈希依然100%(HansBug:实测6001组数据,跑了快两小时有木有啊啊啊啊 wnjxyk:What Ghost? HansBug:我家电脑渣不解释^_^)

    (HansBug:呵呵哒BZOJ3098这题我居然上来就WA了,现在看来这究竟是什么样的神人品啊)

    结果已经了然,而且从bat上运行的时间来看,当N=100000 M=100时,哈希的速度比trie树看样子明显快——估计是虽然trie树可以达到O(NM),但是假如需要新建大量的点的话,那样势必相当费时,多半慢在这上面了,而哈希就是该怎么玩怎么玩——更重要的是——哈希,绝对不等同于非得开一个巨大的数组瞎搞,比如这个例子中直接排个序就完事啦。更重要的是双值哈希的稳定性还是相当不错滴!!!^_^


    后记:以前我曾经一度认为hash算法一定就是必然伴随着一个硕大的数组(HansBug:搞不好还MLE有木有TT  bx2k:那是必然),其实它的灵活性远远超出了我的预想,今天也算是大长了见识;还有祝愿BZOJ3099(Hash Killer III)永远不要有人AC!!!否则那就基本意味着哈希算法的终结了TT

    附:对拍用的bat模板,纯手写的哦,如有雷同绝无可能么么哒

     1 @echo off
     2 set /a s=0
     3 :1
     4 set /a s=s+1
     5 echo Test %s%
     6 rem 此处两个数分别代表N和M,手动修改下即可
     7 echo 10000 100|hs.exe
     8 copy hs.in hashhs%s%.in >nul
     9 copy hsS.out hashhs%s%.out >nul
    10 echo.|time
    11 type hashhs%s%.in|hash.exe >hashhs%s%.ou
    12 echo.|time
    13 fc hashhs%s%.ou hashhs%s%.out >hash
    es%s%.txt
    14 fc hashhs%s%.ou hashhs%s%.out
    15 goto 1
  • 相关阅读:
    【华磊随笔】一个企业的内部讨论(含高层)“30岁IT人,产出代码还是产出思想”之我见。
    dotNetGrid 演示站点(new)争取做ASP.NET中最好的Ajax开源表格控件
    【华磊随笔】进一步的分析:开发者版本;你属于哪个版本的程序员?
    新增格式化单元格数据功能dotNetFlexGrid1.24,快速的设置您的表格数据的不同样式;现在开始扔掉你的Asp.net GridView吧。(更新FieldFormatorHandle参数)
    【华磊随笔】技术到底值不值钱,到底值多少钱有感于公司新产品的上线
    【华磊原创】某企业前两年的开发人员技术能力评估表(不分语言)相对比较全面也比较细
    【华磊原创】安装程序自动检测安装.Net Framework运行环境(使用InnoSetup)
    mainline.js主线
    JS中的“!!”
    读“周大师代码评审”有感
  • 原文地址:https://www.cnblogs.com/HansBug/p/4288118.html
Copyright © 2011-2022 走看看