算法竞赛中的杂项算法

       

目录

1. 异或哈希


        持续更新中喔~~~,如果有错误欢迎提出噢^_^。

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,2^m-1],一个数等于x的概率为\frac{1}{2^m}),这n个数异或为0的概率为\frac{1}{2^m}

        证明:

        ①对于n=1的情况,概率明显为\frac{1}{2^m}

        ②对于n=2的情况,也即两个随机数相等,概率明显也为\frac{1}{2^m}

        ③对于n>=3的情况,有a1^a2=a3^...^an。因为每个a等于每个值的概率都相等,明显这是古典概型。考虑所有的情况总数,有(2^m)^n种情况(每个ai有2^m种情况)。枚举a3到an的所有情况总数有(2^m)^{n-2}种,对于每种情况,设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位共2^m种情况。所以异或为0的情况总数为(2^m)^{n-1},所以异或为0的概率为\frac{1}{2^m}

        证毕。

        其实有一种更简单的证明方法,但不是那么严谨,就是因为这n个数是随机数,那么这n个数的异或结果应该也是足够随机的,所以异或为0的概率为\frac{1}{2^m}

        所以将1到1e6范围内的每个数字哈希(或者说映射)到一个随机的64位数字上后,替换原数组,那么此时,对于每个区间,如果每个数字的出现次数不为偶数,考虑那些出现次数为奇数的数字,因为哈希了,所以最终这些数字就是一个随机的64位数,异或为0的概率为\frac{1}{2^m},也即\frac{1}{2^{64}},这是一个很小的概率,如果数组在哈希后,一个区间异或为0了,我们更应该相信每个数字出现次数为偶数次,而不是有些数字出现次数为奇数,因为这种情况的概率为\frac{1}{2^{64}},实在太小了。而每个数字出现次数为偶数次时,异或为0的概率为1。

        你可能想问:n个最高位为m的随机数,异或为0的概率为\frac{1}{2^m},那么最开始的时候每个数的值域为[1,1e6],那异或为0的概率不就为\frac{1}{1e6}了吗,这也足够小了,为什么还要哈希呢?因为最开始的数据是出题人给你的呀,不是随机的噢^_^。

        代码如下:

     

#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,即需要计算将10^n哈希到10^m范围的数时,出现冲突的概率。考虑计算不出现冲突的概率。对于第一个数,可以随便选,不出现冲突的概率为\frac{10^m}{10^m}(10^m中选择,每种都能选);第二个数,为了不出现冲突,只能少选一个数,不能和第一个数选一样的,不出现冲突的概率为\frac{10^m-1}{10^m};同理,第三个数不能和第一个数、第二个数选一样的,概率为\frac{10^m-2}{10^m}。所以10^n个数不出现冲突的概率为:

        \frac{10^m*(10^m-1)*...*(10^m-10^n+1)}{(10^m)^n}>1*(1-\frac{1}{10^m})*...*(1-\frac{10^n+1}{10^m})>1*(1-\frac{1}{10^m})*...*(1-\frac{10^n}{10^m})>1*(1-\frac{10^n}{10^m})*...*(1-\frac{10^n}{10^m})=(1-\frac{10^n}{10^m})^{10^n}=(1-\frac{1}{10^{12}})^{10^6}=\sum_{k=0}^{10^6}\binom{10^6}{k}(-\frac{1}{10^{12}})^k=1+\sum_{k=1}^{10^6}\binom{10^6}{k}(-\frac{1}{10^{12}})^k>1-\sum_{k=1}^{10^6}\binom{10^6}{k}(\frac{1}{10^{12}})^k=1-\sum_{k=1}^{10^6}\frac{10^6*...*(10^6-k+1)}{k!}(\frac{1}{10^{12}})^k>1-\sum_{k=1}^{10^6}(10^6)^k(\frac{1}{10^{12}})^k=1-\sum_{k=1}^{10^6}(\frac{1}{10^{6}})^k\approx1-\frac{1}{10^6}

        所以出现冲突的概率小于\frac{1}{10^6},可以认为不会出现冲突。

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值