和哈希

carp是真的菜,啥也不会,啥也得学。>_<

生成随机数

好好好,为了学和哈希我们还得再先学一个知识。

你还在用 srand 和 rand 生成随机数,就会在和哈希这里滑坡,下面说一中新的生成随机数的方法。

这个随机数的生成质量甩 rand() 几条街。速度快质量高。

下面的 name 是可以让你自己定义的一个名字,后面的括号里面通常都是写 time(0) 然后你也可以选择自己喜欢的一个数字(114514 的那个别跑,打你啊(just kidding))(其实这个位置你也可以选择不填,不填的话这个种子就是默认随机种子)。

下面是一个类似的用法。

mt19937 name( seed );

cout << name() << endl;

和哈希

introduction

给定长度为 n 的序列 A,每次给出两个长度相等的区间\([l_1, r_1]\)\([l_2, r_2]\),判断这两个区间排序后是否相等。

我们很难找出来一个数据结构去实现这样的操做,但是我们发现两个区间排序后是否相等我们可以转化为一个数字两个区间出现的次数是否相等

我们就可以通过这个性质来给这个区间进行一个赋值。让这个区间的哈希值只和数值有关,和位置没有关系。那么这两个区间的相等的必要条件就成了\(h(l_1, r_2) = h(l_2, r_2)\)。现在问题就变成了我们如何通过构造一个哈希使得这个冲突尽可能的小。

我们的哈希还不能设计的复杂度太高,不然会喧宾夺主,成为我们之后需要去优化地方。

下面是一个设计好的哈希,这个冲突基本很小,不会被卡:

\[h(l, r) = \sum_{i=l} ^ {r} a_i^5 \bmod 998244353 \]

这种方法就叫做和哈希,我们在判定一个性质的时候可以构造一个必要但不充分条件,其中不充分条件有很大的概率,这个时候采用和哈希可以提高正确率,字符串哈希也是同理。

下面是关于和哈希的几个练习题目:

以 T1 为例,讲述一下和哈希的用途。

T1

115. 对称与科学美

出现偶数次 等价于 \(\bigoplus_{i=l}^{r}a_i = 0\) 但是满足这个条件的不一定是出现偶数次,比如说 $ 1 \bigoplus 2 \bigoplus 3 = 0$,这个时候套用和哈希。

之后就是记录前缀异或等于 pre,pre 表示当前前缀的异或,然后直接扫描一遍。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define rl register ll
mt19937 rnd(1919810);

const ll N = 3e5 + 10;

ll n, a[N], ans;

unordered_map<ll, ll> cnt, mp;

int main()
{
    cin >> n;
    
    ll st = 126326;
    for(rl i=1; i <= n; ++ i)
    {
        cin >> a[i];
        if(mp.find(a[i]) == mp.end()) mp[a[i]] = (st += rnd());
        a[i] = mp[a[i]];
    }
    
    ll pre = 0; cnt[0] = 1;
    for(rl i=1; i <= n; ++ i)
    {
        pre ^= a[i];
        ans += cnt[pre];
        cnt[pre] ++ ;
    }
    
    cout << ans << endl;
    return 0;
}

T2

csps 2022 星战

这个题目的发动反攻时机等价于我们的这个图变成了一个内向基环树,每个点的出度都是 1。我们之后推出来,出度和为 n,同时得到入度和为 n。在这个题目上维护入度和是一件很简单的事情。所以我们得到,内向基环树的入度和一定为 n,但是入度和为 n 的不一定是内向基环树,这个时候我们就可以想着和哈希了。

我们的和哈希可以用来给每一个点赋予权值,这一步的想法是从下面这个式子来的:

我们维护一个 \(r(v)\) 来表示所有指向 \(v\) 这个点的点权和,我们可以得到下面这个式子。

\[r(v) = \sum w(u) \]

我们的和哈希就让原来维护入度的问题变成了维护点权和的问题。

code
#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define rl register ll
mt19937 rnd(time(0));

template <class T>

inline void read(T &res)
{
    char ch; bool f = 0;
    while((ch = getchar()) < '0' || ch > '9') f |= ch == '-';
    res = ch ^ 48;
    while((ch = getchar()) <= '9' && ch >= '0') res = (res << 1) + (res << 3) + (ch ^ 48);
    res = f ? ~res + 1 : res;
}

const ll N = 5e5 + 10, M = N;

ll n, m;

ll d[N], w[N], r[N], sum, now, g[N];

int main()
{
    // freopen("1.in", "r", stdin), freopen("1.out", "w", stdout);

    read(n), read(m);

    for(rl i=1; i <= n; ++ i)
        w[i] = rnd(), sum += w[i];
    
    for(rl i=1; i <= m; ++ i)
    {
        ll a, b; read(a), read(b);
        d[a] ++ ; r[b] += w[a], g[b] = r[b], now += w[a];
    }


    ll q; read(q);

    while(q -- )
    {
        ll a, b, t;
        read(t), read(a);
        switch(t)
        {
            case 1 :
                read(b), r[b] -= w[a], now -= w[a];
                break;
            case 2 : 
                now -= r[a], r[a] = 0;
                break;
            case 3 : 
                read(b), r[b] += w[a], now += w[a];
                break;
            case 4 : 
                now += g[a] - r[a], r[a] = g[a];
        }

        (now == sum) ? puts("YES") : puts("NO");
    }
    return 0;
}

T3

Kazaee

这个题目是T1的加强版本?上面是只统计出现偶数次的,下面这个是统计询问中给定的次数,下面这个还带有修改操作。而上面这个是已经给定了。
下面这个是我写的题解

posted @ 2023-10-09 13:32  carp_oier  阅读(151)  评论(0)    收藏  举报