哈希



前言

这周作业有点多呀然后就没什么时间学,加上一开始看的是LCS然后又啃不下,就晚了点发,感觉期末30篇的flag会打脸_(xз」∠)_
Hash思维,其实平时经常会用到,只是自己没有意识用到了而已,比如上学期在做“Anagram单词”的时候,舍友把单词中的各个字母*1000累加成一个值,通过这个值来判断,本质上就是Hash思维嘛
另外这次看Hash感觉比较不可思议的是,再次用到了图论!
还有是Hash本身很多可以用STL中的map代替,但是容易超时,就看题目想不想卡你

  1. Hash表
    https://blog.csdn.net/duan19920101/article/details/51579136
  2. ELFhash
    https://blog.csdn.net/ltyqljhwcm/article/details/52966874
  3. Hash字符串匹配
    https://www.cnblogs.com/zyf0163/p/4806951.html
  4. 素数表
    https://blog.csdn.net/zhhx2001/article/details/52150810
  5. 为什么要余一个质数?
    https://segmentfault.com/q/1010000000593741

Equations - HDU 1496


题目大意
给定a,b,c,d,问方程 ax1^2+bx2^2+cx3^2+dx4^2=0在x属于[-100,0)U(0,100]有多少种解?

思路
最朴素的思想当然是四重循环啦,O(n^4)稳稳的超时
然后比较容易想到的是先枚举一个x,这里用x4举栗子。先枚举dx4^2,然后结果存数组下标,值记录为true,然后三重循环查最后的-dx4^2是否存在,O(n^3),超不超时没试过
本题用Hash可以优化到O(n^2),枚举ax1^2+bx2^2然后因为范围太大了所以Hash,然后再枚举cx3^2+dx4^2查找有没有-(ax1^2+bx2^2)在表中,统计答案就可以了
另外本题有个小技巧,可以枚举[1,100],最后的答案为 16*ans,因为平方嘛

  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #include <cstring>
  using namespace std;
  const int N = (int)1e5;
  const int M = 50;
  const int MOD = 1 << 15;

  struct edge{
      int val;
      int cnt;
      int next;
  };
  edge e[MOD<<1];
  int tot;
  int head[MOD<<1];

  void init(){
      memset(head, -1, sizeof(head));
      tot = 0;
  }

  void hashInsert(int val){
      int u = val%MOD + MOD;    //+MOD是因为有负数
      for(int i = head[u]; ~i; i = e[i].next){
          if(e[i].val == val){
              e[i].cnt++;
              return;
          }
      }

      e[tot].cnt = 1;           //和图论中的加边代码是一样的,毕竟是简易版链表
      e[tot].val = val;
      e[tot].next = head[u];
      head[u] = tot++;
  }

  int hashFind(int val){
      int u = val%MOD + MOD;    
      for(int i = head[u]; ~i; i = e[i].next){
          if(e[i].val == val)    return e[i].cnt;
      }
      return 0;
  }

  int main(){
      int a, b, c, d;
      while(~scanf("%d%d%d%d", &a, &b, &c, &d)){
          init();
          int sum = 0;
          for(int x = 1; x <= 100; x++){
              for(int y = 1; y <= 100; y++){
                  hashInsert(a*x*x + b*y*y);
              }
          }
          for(int x = 1; x <= 100; x++){
              for(int y = 1; y <= 100; y++){
                  sum += hashFind(-c*x*x - d*y*y);
              }
          }
          printf("%d\n", sum << 4);
      }
  }


Negative and Positive (NP) - HDU-5183


题目大意
给定数组a和数字k,求是否存在任意的a[i]−a[i+1]+a[i+2]+⋯+(−1)^(j−i)a[j]使得其结果等于k

思路
在51NOD做过类似的题然后完全没有意识,自己太菜 _(:_」∠)_
可以记录前缀和,那么这时方程化为 sum[j] - sum[i - 1] = k (i为奇数) 或者 -(sum[j] - sum[i - 1]) = k (i为偶数),如果双重循环照样超时
可将方程化继续化为sum[j] = k + sum[i - 1] (i为奇数) 或者 sum[j] = -k + sum[i - 1] (i为偶数),这时候就可以先求出前缀和sum[i]然后hash,再循环一遍查找是否存在值 k + sum[i - 1] (i为奇数) 或者 sum[j] = -k + sum[i - 1] (i为偶数)
此处hash的原因还是因为范围太大,需要缩小范围再存入数组,记得hash时还要存入前缀和当前i的位置,查找的时候还要满足j >= i

  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #include <cstring>
  #include <map>
  using namespace std;
  const int N = (int)1e6 + (int)1e5;
  const int MOD = 999983;
  typedef long long ll;

  struct edge{
      ll val, pos, next;
  };
  edge e[N];
  int head[N << 1];
  int tot;
  ll a[N];

  inline void init(){
      memset(head, -1, sizeof(head));
      memset(a, 0, sizeof(a));
      tot = 0;
  }

  inline void hashInsert(ll val, int pos){
      int u = val%MOD + MOD;
      e[tot].val = val;
      e[tot].pos = pos;
      e[tot].next = head[u];
      head[u] = tot++;
  }

  bool hashFind(ll val, int pos){
      int u = val%MOD + MOD;
      for(int i = head[u]; ~i; i = e[i].next){
          if(e[i].val == val && e[i].pos >= pos)    return true;
      }
      return false;
  }

  int main(){
      int t;
      int caseno = 1;
      scanf("%d", &t);
      while(t--){
          init();
          int n;
          ll  k;
          bool flag = false;
          scanf("%d%lld", &n, &k);
          for(int i = 1; i <= n; i++){
              scanf("%lld", &a[i]);
              if(i&1){
                  a[i] = a[i - 1] + a[i];
              }else{
                  a[i] = a[i - 1] - a[i];
              }
              hashInsert(a[i], i);
          }
          for(int i = 1; i <= n && (!flag); i++){
              if(i&1){
                  flag = hashFind(a[i - 1] + k, i);
              }else{
                  flag = hashFind(a[i - 1] - k, i);
              }
          }
          printf("Case #%d: ", caseno++);
          if(flag){
              puts("Yes.");
          }else{
              puts("No.");
          }
      }
      return 0;
  }


Flying to the Mars - HDU 1800


题目大意
士兵学骑扫帚,等级高的可以教等级低的,并且可以传递,比如给定士兵等级A>B>C=D>E,那么A教B,B教C,C教E他们共用一把扫帚,D只能再用一把扫帚,最少总共需要两把扫帚。现在给定士兵等级,求最少需要用几把扫帚

思路
本题求的是出现重复数字中重复数量的最大值,重点在于”less than 30 digits”,说明不能用unsigned long long存,得用字符串,那就得用hash了,注意去掉前导0,这里因为是要hash一个字符串所以选用比较简单的ELFHash,hash的时候顺便统计一下数量,维护数量最大值,hash完成输出就可以了

  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #include <cstring>
  #include <map>
  using namespace std;
  const int N = 3005;
  const int MOD = 7003;
  typedef long long ll;

  struct edge{
      int next;
      int cnt;
      char str[35];
  };
  edge e[N];
  int head[MOD + 5];
  int tot;

  inline int ELFhash(char *str){      //具体看上面的博文
      unsigned long val = 0;
      unsigned long x;
      while(*str){
          val = (val << 4) + * str;
          x = val & 0xf0000000;     
          if(x){
              val ^= (x >> 24);    
              val &= ~x;
          }
          str++;
      }
      return val;
  }

  inline void init(){
      memset(head, -1, sizeof(head));
      tot = 0;
  }

  inline void hashInsert(char* str, int& ans){
      while(*str == '0')  str++;      //去前导0
      int u = ELFhash(str)%MOD + 1;
      for(int i = head[u]; ~i; i = e[i].next){
          char* vstr = e[i].str;
          if(strcmp(vstr, str) == 0){
              e[i].cnt++;
              ans = max(ans, e[i].cnt);
              return;
          }
      }
      e[tot].cnt = 1;
      ans = max(ans, 1);
      strcpy(e[tot].str, str);
      e[tot].next = head[u];
      head[u] = tot++;
  }

  int main(){
      int n;
      char str[35];
      while(~scanf("%d", &n)){
          init();
          getchar();
          int ans = 0;
          while(n--){
              gets(str);
              hashInsert(str, ans);
          }
          printf("%d\n", ans);
      }
      return 0;
  }


Oulipo - POJ 3461


题目大意
给定W串,问其在T串中出现了几次

思路
这题之前用KMP算法做过,其实也可以用hash做,比较迷醉的就是不一定能对,因为hash值有小概率相同,万一错了怎么办?换个base值试试
具体原理看上面博文
个人不太清楚的是:就算溢出也能保证值相同?可以用数论结论推一下但是题目能过我就懒得推了

  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #include <cstring>
  #include <map>
  #include <queue>
  using namespace std;
  typedef long long ll;
  typedef unsigned long long ull;
  const int N = 1e6 + 15;
  const int MOD = 1 << 20;
  const int M = 10005;
  const ull base = 1e8 + 7;

  ull  p[N];
  ull  g[N];
  char a[M], b[N];

  inline void init(){
      p[0] = 1;
      for(int i = 1; i < N; i++)      p[i] = p[i - 1] * base;
  }

  int main(){
      init();
      int t;
      scanf("%d", &t);
      while(t--){
          scanf("%s%s", a + 1, b + 1);
          int lena = strlen(a + 1), lenb = strlen(b + 1);
          int ans = 0;

          ull aval = 0;
          g[lenb + 1] = 0;
          for(int i = lena; i >= 1; i--){
              aval = aval*base + (a[i] - 'A');    //W串的hash值,a[0] * base^0 + a[1] * base^1 + .....
          }
          for(int i = 1; i <= lenb; i++){
              g[i] = g[i - 1] + (b[i] - 'A') * p[i - 1];      //T串前缀和的hash, g[i] = b[0] + b[1] * base^1 + ... + b[i] * base^i
          }
          for(int i = 1; i + lena - 1 <= lenb; i++){
              int l = i, r = i + lena - 1;
              ull bsval = g[r] - g[l - 1];                    //在l到r区间的hash就为 g[r] - g[l - 1]
              if(bsval == aval * p[l - 1])   ans++;           //aval要乘上 p[l - 1],对齐上面的区间
          }
          printf("%d\n", ans);
      }
      return 0;
  }