目录
持续更新中喔~~~,如果有错误欢迎提出噢^_^。
1. 异或哈希
先来看这么一道题:https://codeforces.com/contest/2014/problem/H。题意大致为:给你一个数组,大小为n(n<=2e5),每个元素的值域为[1,1e6]。q(q<=2e5)个查询,每次查询一个区间,问这个区间每个出现了的数字的出现次数是否都是偶数次。
提示:这道题也可以使用莫队算法,如果有兴趣可以去了解一下。
仔细思考这道题,一个区间如果每个数都出现偶数次,那么有所有数的异或为0,但是是否是异或为0,每个数都出现偶数次呢?明显不是的,比如1^2=5^6。那是不是这种思路就不行了呢?雀氏,异或为0,不能说明每个数出现偶数次。但是如果每个数都是随机数的话,这就很有可能了。有如下结论:
有n(n>=1)个最高位为m的随机数(比如n个unsigned int,最高位为32,这里的最低位从1开始)(每个数的值域为[0,],一个数等于x的概率为
),这n个数异或为0的概率为
。
证明:
①对于n=1的情况,概率明显为。
②对于n=2的情况,也即两个随机数相等,概率明显也为。
③对于n>=3的情况,有a1^a2=a3^...^an。因为每个a等于每个值的概率都相等,明显这是古典概型。考虑所有的情况总数,有种情况(每个ai有
种情况)。枚举a3到an的所有情况总数有
种,对于每种情况,设a3^...^an=x。现在有a1^a2=x,需要计算a1和a2的总情况数。对于x的每一位,如果为0,那么在这一位上有a1=a2=0或者1;如果为1,那么在这一位上有a1=1,a2=0或者a1=0,a2=1。可以看出对于x的每一位,不管为0还是1,a1,a2在这一位的选择都只有2种情况,所以m位共
种情况。所以异或为0的情况总数为
,所以异或为0的概率为
。
证毕。
其实有一种更简单的证明方法,但不是那么严谨,就是因为这n个数是随机数,那么这n个数的异或结果应该也是足够随机的,所以异或为0的概率为。
所以将1到1e6范围内的每个数字哈希(或者说映射)到一个随机的64位数字上后,替换原数组,那么此时,对于每个区间,如果每个数字的出现次数不为偶数,考虑那些出现次数为奇数的数字,因为哈希了,所以最终这些数字就是一个随机的64位数,异或为0的概率为,也即
,这是一个很小的概率,如果数组在哈希后,一个区间异或为0了,我们更应该相信每个数字出现次数为偶数次,而不是有些数字出现次数为奇数,因为这种情况的概率为
,实在太小了。而每个数字出现次数为偶数次时,异或为0的概率为1。
你可能想问:n个最高位为m的随机数,异或为0的概率为,那么最开始的时候每个数的值域为[1,1e6],那异或为0的概率不就为
了吗,这也足够小了,为什么还要哈希呢?因为最开始的数据是出题人给你的呀,不是随机的噢^_^。
代码如下:
#include<bits/stdc++.h>
#include<random>
using namespace std;
typedef unsigned long long ull;
const int N=1e6+50;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
ull mp[N];
void solve(){
int n,q;
cin>>n>>q;
vector<ull>val(n+1);
for(int i=1;i<=n;i++){
int a;
cin>>a;
val[i]=val[i-1]^mp[a];
}
while(q--){
int l,r;
cin>>l>>r;
cout<<(val[r]==val[l-1]?"yes":"no")<<endl;
}
}
int main(){
for(int i=1;i<=1000000;i++){
mp[i]=rng();
}
int t;
cin>>t;
while(t--)
solve();
}
其中rng函数用于生产一个unsigned long long的随机数。
这里还需要保证每个数哈希到的数是不同的,也即mp[i]互不相同,当然这里出现相同的概率也很低。具体地,1e6个数哈希到1e18范围的数时,出现冲突的概率是很低的。
解:这里另n=6,m=18,即需要计算将哈希到
范围的数时,出现冲突的概率。考虑计算不出现冲突的概率。对于第一个数,可以随便选,不出现冲突的概率为
(
中选择,每种都能选);第二个数,为了不出现冲突,只能少选一个数,不能和第一个数选一样的,不出现冲突的概率为
;同理,第三个数不能和第一个数、第二个数选一样的,概率为
。所以
个数不出现冲突的概率为:
所以出现冲突的概率小于,可以认为不会出现冲突。