ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Hash小技巧

2022-08-14 00:04:28  阅读:103  来源: 互联网

标签:return 技巧 int ll Hash hash const


在namo-Camp里dls讲了一些Hash技巧,嘻嘻,记录一下。

 

零、Neal_Hash的模板

这是一个比较稳定的Hash模板,可以用于unordered_map,也可以当成随机数作映射(集合Hash有用)。

neal_hash

struct custom_hash {
  static uint64_t splitmix64(uint64_t x) {
    // http://xorshift.di.unimi.it/splitmix64.c
    x += 0x9e3779b97f4a7c15;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
    x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
    return x ^ (x >> 31);
  }
  size_t operator()(uint64_t x) const {
    static const uint64_t FIXED_RANDOM =
        chrono::steady_clock::now().time_since_epoch().count();
    return splitmix64(x + FIXED_RANDOM);
  }
};
// 如果使用的是 pair ,那么应该这样调用
struct pairHash {
  static uint64_t splitmix64(uint64_t x) {
    x += 0x9e3779b97f4a7c15;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
    x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
    return x ^ (x >> 31);
  }
  size_t H(uint64_t x) const {
    static const uint64_t FIXED_RANDOM =
        chrono::steady_clock::now().time_since_epoch().count();
    return splitmix64(x + FIXED_RANDOM);
  }
  size_t operator()(const std::pair<int, int> &p) const {
    size_t h1 = H(p.first), h2 = H(p.second);
    return (h1 << 31) ^ (h2 >> 31);
  }
};

再来一个比较稳定的随机数模板:

mt19973+uniform_int_distribution

inline int randInt(int l, int r) {
  // static mt19937 rng(time(0));
  static mt19937 rng(chrono::steady_clock::now().time_since_epoch().count() ^ rand());
  uniform_int_distribution<int> dis(l, r);
  return dis(rng);
}

 

一、字符串Hash


给出一个比较难卡掉的随机双哈希模板。(有时候快速模会比较快,有时候会比较慢,具体看机器)

记得提前initPow()!!!!

查看代码
 struct FastMod {  // 要 mod 的数不可以是负数,要保证细节
  ull b, m;
  FastMod(ull b) : b(b), m((ull)((__int128_t(1) << 64) / b)) {}
  friend ull operator%(const ull& a, const FastMod& mod) {
    ull r = (ull)(a - (__int128_t(mod.m) * a >> 64) * mod.b);
    return r >= mod.b ? r - mod.b : r;
  }
} m1(1e9 + 3), m2(1e9 + 9);
 

inline int randInt(int l, int r) {
  // static mt19937 rng(time(0));
  static mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
  uniform_int_distribution<int> dis(l, r);
  return dis(rng);
}

ll qpow(ll x, ll y, ll M) {
  ll r = 1;
  for (; y > 0; y >>= 1, x = (x * x) % M)
    if (y & 1) r = (r * x) % M;
  return r;
}

#define inv(x, y) (qpow(x, y - 2, y)) 

ll m1 = 1e9 + 7, m2 = 998244353;
struct hashv {
  ll v1, v2;
  hashv() {}
  hashv(ll a) { v1 = v2 = a; }
  hashv(ll a, ll b) { v1 = a, v2 = b; }
  hashv operator*(ll val) const { return {(v1 * val) % m1, (v2 * val) % m2}; }
  hashv operator+(ll val) const { return {(v1 + val) % m1, (v2 + val) % m2}; }
  hashv operator-(ll val) const { return {(v1 + m1 - val) % m1, (v2 + m2 - val) % m2}; }
  hashv operator*(const hashv& rhs) const { return {(v1 * rhs.v1) % m1, (v2 * rhs.v2) % m2}; }
  hashv operator/(const hashv& rhs) const { return (*this) * hashv(inv(rhs.v1, m1), inv(rhs.v2, m2)); }
  hashv operator+(const hashv& rhs) const { return {(v1 + rhs.v1) % m1, (v2 + rhs.v2) % m2}; }
  hashv operator-(const hashv& rhs) const { return {(v1 + m1 - rhs.v1) % m1, (v2 + m2 - rhs.v2) % m2}; }
  hashv operator^(const hashv& rhs) const { return {(v1 ^ rhs.v1) % m1, (v2 ^ rhs.v2) % m2}; }
  bool operator==(const hashv& rhs) const { return v1 == rhs.v1 && v2 == rhs.v2; }
  bool operator<(const hashv& rhs) const {
    if (v1 != rhs.v1) return v1 < rhs.v1;
    return v2 < rhs.v2;
  }
  friend hashv qpow(hashv x, ll num) {
    hashv ret(1);
    for (; num > 0; num >>= 1, x = x * x)
      if(num & 1) ret = ret * x;
    return ret;
  }
} base(randInt(31, 1e9), randInt(33, 5e8));

// 预处理指数
hashv pw[maxn];
void initPow() {
  pw[0] = {1, 1};
  for (int i = 1; i < maxn; i++) pw[i] = pw[i - 1] * base;
}

// 正向Hash: [str[l]*base^0, ...., str[r]*base^(r-l+1)]
hashv P[maxn];
hashv getP(int l, int r) { return P[r] - P[l - 1] * pw[r - l + 1]; }
void preHash(int n, char str[], hashv h[]) { // 使用霍纳法则来算
  h[0] = {0, 0};
  for (int i = 1; i <= n; i++) h[i] = h[i - 1] * base + str[i];
}

// 反向Hash: [str[l]*base^(r-l+1), ...., str[r]*base^0]
hashv S[maxn];
hashv getS(int l, int r) { return S[l] - S[r + 1] * pw[r - l + 1]; }
void sufHash(int n, char str[], hashv h[]) {
  h[n + 1] = {0, 0};
  for (int i = n; i >= 1; i--) h[i] = h[i + 1] * base + str[i];
}

 

常见的应用:

(1)判断两个字符串是否相同。

(2)正反Hash判断字符串是不是回文串。

(3)判断一个字符串是否是另一个字符串的子串!很多时候这种做法可以做到\(O(n)\)快速解决题目,例题:Keyboard Warrior【杭电多校-提交

(4)其实字符串hash不一定局限在字符,比如上面多校这题把(ch, num)二元组进行hash也行 - 当然std是利用了等比数列求和来算这个二元组hash。

 

二、集合Hash


dls给出了几种hash的方法,然后又从zdq那里得知neal_hash也可以拿来当随机数!

 

题目1:E - Prefix Equality 【对前缀做不可重集合Hash】

可能是数据比较水,集合hash的异或/加法两种做法都可以过去。

查看代码
struct custom_hash {
  static uint64_t splitmix64(uint64_t x) {
    x += 0x9e3779b97f4a7c15;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
    x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
    return x ^ (x >> 31);
  }
  size_t operator()(uint64_t x) const {
    static const uint64_t FIXED_RANDOM =
        chrono::steady_clock::now().time_since_epoch().count();
    return splitmix64(x + FIXED_RANDOM);
  }
} gethash;
 
unordered_map<int, uint64_t, custom_hash> ID;
unordered_set<int> A, B;
int n, q;
uint64_t a[maxn], b[maxn];
 
void solve() {
  cin >> n;
  A.clear(), B.clear();
  for (int i = 1, x; i <= n; i++) {
    cin >> x;
    if (A.insert(x).se) {
      a[i] = gethash(x) ^ a[i - 1];
    } else {
      a[i] = a[i - 1]; 
    }
  }
  for (int i = 1, x; i <= n; i++) {
    cin >> x;
    if (B.insert(x).se) {
      b[i] = gethash(x) ^ b[i - 1];
    } else {
      b[i] = b[i - 1]; 
    }
  }
  cin >> q;
  while (q--) {
    int l, r;
    cin >> l >> r;
    cout << (a[l] == b[r] ? "Yes" : "No") << endl;
  }
}

 

 

三、树上Hash


一般用于判断树同构问题,除此之外,树同构问题可以使用AHU算法解决。

(1)有根树Hash

  • 有根树hash是\(O(n)\)的。

(2)无根树Hash

  • 无根树最多只有两个重心,求出重心分别hash一遍,如果都相等,那么很大概率是同构。

 

下面给出一个比较难卡的方法(从OI-WIKI上面抄)。

简单朴素版:Hash的计算公式:\(Hash[x] = randSeed + \sum Hash[v]*prime[size[v]] \)

稍微加强版:如果题目数据很强,那么需要对Hash再做一遍封装处理(调用pack函数):Hash[x] = pack(Hash[x], size[x])

其中pack有许多种写法,OI-WIKI给出的是 pack(a, b): return 2ull + 3ull * a + 7ull * (prime(b + 1) ^ prime(b + 2))(注意,后面一坨怎么写都行,就是不要写成prime[b],不知道为什么会被卡)

模板

namespace TreeHash {
/********  Prime  ********/
const int N = 2e6 + 11;  // n=1e5时,质数要筛到2e6,n=2e5时,质数要筛到4e6.
int prime[N], tot;
bool vis[N];
void initPrime() {
  tot = 0;
  for (int i = 2; i < N; i++) {
    if (!vis[i]) prime[++tot] = i;
    for (int j = 1; j <= tot && i * prime[j] < N; j++) {
      vis[i * prime[j]] = 1;
      if (i % prime[j]) break;
    }
  }
}
/******** TreeHash ********/
ull src_hash[maxn], pac_hash[maxn];
// 计算方式 (randSeed 可以直接设为1,影响不大)
// 第一步, src_hash = randSeed + pac_hash[son[0]] * prime[sz[son[0]]] + ...
// 第二步, pac_hash[x] = pack(src_hash[x])
ull pack(ull hash_val, int sz) {
  // return 2 + 3 * hash_val + 7ull * prime[sz + 1];  // 这个也行
  return 2 + 3 * hash_val + 7ull * (prime[sz + 1] ^ prime[sz - 1]);
}
}  // namespace TreeHash
using namespace TreeHash;

 

题目1:无根树同构模板题

直接求出重心然后Hash。

查看代码

namespace TreeHash {
/********  Prime  ********/
const int N = 2e6 + 11;  // n=1e5时,质数要筛到2e6
int prime[N], tot;
bool vis[N];
void initPrime() {
  tot = 0;
  for (int i = 2; i < N; i++) {
    if (!vis[i]) prime[++tot] = i;
    for (int j = 1; j <= tot && i * prime[j] < N; j++) {
      vis[i * prime[j]] = 1;
      if (i % prime[j]) break;
    }
  }
}
/******** TreeHash ********/
ull src_hash[maxn], pac_hash[maxn];
// 计算方式 (randSeed 可以直接设为1,影响不大)
// 第一步, src_hash = randSeed + pac_hash[son[0]] * prime[sz[son[0]]] + ...
// 第二步, pac_hash[x] = pack(src_hash[x])
ull pack(ull hash_val, int sz) {
  // return 2 + 3 * hash_val + 7ull * prime[sz + 1];  // 这个也行
  return 2 + 3 * hash_val + 7ull * (prime[sz + 1] ^ prime[sz - 1]);
}
}  // namespace TreeHash
using namespace TreeHash;

int n, bigson[maxn], sz[maxn], mn_size, curid;
vector<vector<int>> e;

void predfs(int x, int f) {
  sz[x] = 1, bigson[x] = 0;
  for (int& v : e[x]) {
    if (v == f) continue;
    predfs(v, x);
    sz[x] += sz[v];
    if (!bigson[x] || bigson[x] < sz[v]) bigson[x] = sz[v];
  }
  bigson[x] = max(bigson[x], n - sz[x]);
  mn_size = min(mn_size, bigson[x]);
}

map<ull, int> ID;

void dfs(int x, int fa) {  // 记得重新计算size(不同的根)
  src_hash[x] = 1, sz[x] = 1;
  for (int& v : e[x]) {
    if (v == fa) continue;
    dfs(v, x);
    sz[x] += sz[v];
    src_hash[x] += pac_hash[v] * prime[sz[v]];
  }
  pac_hash[x] = pack(src_hash[x], sz[x]);
}

void solve() {
  cin >> n, curid++;
  e.assign(n + 1, {});
  for (int i = 1, x; i <= n; i++) {
    cin >> x;
    if (!x) continue;
    e[x].emp(i), e[i].emp(x);
  }
  mn_size = inf_int;
  predfs(1, 0);  // 求出重心最大的儿子的大小
  for (int i = 1; i <= n; i++) {
    if (bigson[i] == mn_size) {
      dfs(i, 0);
      if (!ID.count(pac_hash[i])) {
        ID[pac_hash[i]] = curid;
      } else {
        cout << ID[pac_hash[i]] << endl;
        return;
      }
    }
  }
  cout << curid << endl;
}

signed main() {
  initPrime();
  ios_fast;
  int TEST = 1;
  cin >> TEST;
  while (TEST--) solve();
}

 

题目2:Bracket Sequences on Tree【换根DP + 无根树HASH】

麻了,今天调了一天的树上Hash,发现HDU的数据是真的强,只要树Hash用的不是质数Hash方法就会被卡掉。

而且因为质数数量不多,所以n=1e5的时候,筛质数至少要筛到2e6。。。痛苦面具(一开始筛质数筛少了。如果n=1e6的话,这种方法的树Hash基本上是绝对被卡了。。。(至少在HDU上,质数不够的话,是存在数据卡掉的)。

这种Hash方法的好处是可以进行逆运算,实现换根DP的时候很好转移。

换根DP有点小细节,从父节点往子节点转移时,记得乘上inv(dp[v])。

查看代码

vector<int> pr;
bool vis[4000003];

void initPrime() {
  for (int i = 2; i <= 1e6; i++) {
    if (!vis[i]) pr.emp(i);
    for (int& p : pr) {
      if (1ll * p * i >= 1e6) break;
      vis[i * p] = 1;
      if (i % p == 0) break;
    }
  }
}

ll Add(ll x, ll y) { return ((x + y) % mod + mod) % mod; }

ll Mul(ll x, ll y) { return x * y % mod; }

ll fac[maxn], ifac[maxn];
inline void initAC() {
  fac[0] = 1,
  ifac[1] = 1;  // inv[0] = 1,如果只是求逆元,inv[0] = 0
  for (int i = 2; i < maxn; i++)
    ifac[i] = 1LL * (mod - mod / i) * (ifac[mod % i]) % mod;
  ifac[0] = 1;
  for (int i = 1; i < maxn; i++) fac[i] = (fac[i - 1] * i) % mod;
  for (int i = 2; i < maxn; i++) ifac[i] = (ifac[i - 1] * ifac[i]) % mod;
}

int n, sz[maxn], son_cnt[maxn];
ll dp[maxn];
ull srchash[maxn], hashval[maxn];
vector<vector<int>> e;
map<ull, ll> hashcnt[maxn];

ll qpow(ll x, ll y) {
  ll r = 1;
  for (; y > 0; y >>= 1, x = Mul(x, x))
    if (y & 1) r = Mul(r, x);
  return r;
}

ull getPr(int x) {
  return pr[x - 1];
}

ull pack(ull a, int b) {
  return (2ull + 3ull * a + 7ull * (getPr(b + 1) ^ getPr(b + 2)));
}

void dfs1(int x, int f) {
  dp[x] = sz[x] = 1;
  son_cnt[x] = 0;
  hashval[x] = 1;
  for (int& v : e[x]) {
    if (v == f) continue;
    dfs1(v, x);
    hashval[x] += hashval[v] * getPr(sz[v]);
    sz[x] += sz[v];
    son_cnt[x]++;
    dp[x] = Mul(dp[x], dp[v]);
    hashcnt[x][hashval[v]]++;
  }
  srchash[x] = hashval[x];
  hashval[x] = pack(hashval[x], sz[x]);
  dp[x] = Mul(dp[x], fac[son_cnt[x]]);
  for (auto it = hashcnt[x].begin(); it != hashcnt[x].end(); ++it)
    dp[x] = Mul(dp[x], ifac[it->se]);
}

ll ans;
set<ull> hash_st;

void dfs2(int x, int f, ull hash_fa, ll dp_fa) {
  if (f) {
    int pcnt = hashcnt[x][hash_fa];
    hashcnt[x][hash_fa]++;
    son_cnt[x]++;
    dp[x] = Mul(dp[x], dp_fa);
    dp[x] = Mul(Mul(dp[x], ifac[son_cnt[x] - 1]), fac[son_cnt[x]]);
    dp[x] = Mul(Mul(dp[x], fac[pcnt]), ifac[pcnt + 1]);
    srchash[x] += hash_fa * getPr(n - sz[x]);
    hashval[x] = pack(srchash[x], n);
  }
  ll tmp2_dp = Mul(Mul(dp[x], fac[son_cnt[x] - 1]), ifac[son_cnt[x]]);
  if (hash_st.insert(hashval[x]).se) {  // add ans
    ans = Add(ans, dp[x]);
  }
  for (int& v : e[x]) {
    if (v == f) continue;
    int c = hashcnt[x][hashval[v]];
    ll tmp3_dp =
        Mul(Mul(Mul(tmp2_dp, fac[c]), ifac[c - 1]), qpow(dp[v], mod - 2));
    ull tmp_hash = srchash[x] - hashval[v] * getPr(sz[v]);
    dfs2(v, x, pack(tmp_hash, n - sz[v]), tmp3_dp);
  }
}

void solve() {
  cin >> n;
  ans = 0;  // clear
  hash_st.clear();
  for (int i = 1; i <= n; i++) hashcnt[i].clear();
  e.assign(n + 1, {});
  for (int i = 1, x, y; i < n; i++) {
    cin >> x >> y;
    e[x].emp(y), e[y].emp(x);
  }
  dfs1(1, 0);
  dfs2(1, 0, 0, 0);
  cout << ans << "\n";
}

signed main() {
  initPrime();
  initAC();
  ios_fast;
  int TEST = 1;
  // freopen("test_input.txt", "r", stdin);
  // freopen("test_output.txt", "w", stdout);
  cin >> TEST;
  while (TEST--) solve();
}

 

标签:return,技巧,int,ll,Hash,hash,const
来源: https://www.cnblogs.com/guanjinquan/p/16246900.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有