异或哈希

问题:https://codeforces.com/contest/1175/problem/F
关键点:随机化+异或
1.为何要异或:忽略顺序
将1~n随机的一一映射到long long值域内,形成新的映射数组b。再根据异或的特点,只需要判断:
b[1]⊕b[2]⊕…………⊕b[n]==b[a[l]]⊕b[a[l+1]]⊕……⊕b[a[r]]
2.为何要随机,因为若不随机,二进制位数有限,会存在一些情况使得异或并不正确.
例如:1 ⊕ 2 ⊕ 3 ⊕ 4 ⊕ 5 ⊕ 6 ⊕ 7 = 1 ⊕ 2 ⊕ 3 ⊕ 4 ⊕ 5 ⊕ 4 ⊕ 5
其实上述等式成立实质上就是等式两边每一个二进制位上的1的个数都相等。
那么当二进制位数变多了之后,发生错误的概率也会下降(但不会没有,只是可以近似看作没有)
问题二:如何找到所有合法子数组?
考虑以下这几个 合法子数组 一定满足的条件:
1.序列含有且仅含有一个1.
2.合法子数组的最大值就是它的长度
3.最大值要么出现在1的左边,要么出现在1的右边.
那么我们遍历序列每一个1的位置,先假设最大值出现在1的右边,那么循环遍历它右边的位置,同时统计区间最大值。然后对于每一个位置,我们假设它就是某个合法子数组的右端点。然后确定左端点。再O ( 1 ) O(1)O(1)hash判排列。然后再revese一下再跑一遍该算法即可。

#include<bits/stdc++.h>

#define int long long
#define endl '\n'
#define x first 
#define y second

using namespace std;
const int N=5e5+10,mod=998244353;

typedef pair<int,int> PII; 

int n;
int bk[N] , a[N] , b[N] , p[N];

//异或哈希 
void slove(){
	int n; 
	cin>>n;
    srand(9904);
    //将n个数映射到一个ll范围下的桶中
    for (int i = 1 ; i <= n ; i++){
        bk[i] = ((long long)rand()<<30) + 1ll * (rand()<<16) + 1ll * rand();//可以 
        p[i] = p[i - 1] ^ bk[i];//p[i]表示1~i的异或和 
    }
    for (int i = 1 ; i <= n ; i++) cin>>a[i]; 
    int ans = 0;//
    for (int t = 0 ; t < 2 ; t++){//正着跑一遍,倒着跑一遍(正着跑是假定最大值在1的右边,倒着跑是假定最大值在1的左边)

        for (int i = 1 ; i <= n ; i++)
            b[i] = bk[a[i]] ^ b[i - 1]; 

        int now = -1 , mx = 0;//mx记录的是最大值,now表示当前的1的位置
        for (int i = 1 ; i <= n ; i++){
            if (a[i] == 1){
                ans += (t == 0);//特殊记录为1的情况 
                now = i;
                mx = 1;
                continue;
            }
            if (now == -1) continue;// 
            mx = max(mx , a[i]);
            if (mx < (i - now + 1)) continue;//如果mx小于区间长度的话 
            int x = now - (mx - (i - now + 1));//如果mx大于等于区间长度的话,找到左端点 
            if (x <= 0) continue;
            ans += ((b[i] ^ b[x - 1]) == p[mx]);//查看异或和是否相等 
        }
        reverse(a + 1 , a + 1 + n);
    }
    cout << ans << endl;
} 

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int T=1;
//	cin>>T;
	while(T--) slove(); 
}	

习题二:
k进制的异或哈希
代码

#include <bits/stdc++.h>

using namespace std;

#define int long long 
#define endl '\n'

typedef pair<int,int> PII;

const int N=40; 

typedef array<int,N> ehs;
int n,k; 

ehs operator ^(const ehs &a, const ehs &b){
    ehs c;
    for (int i = 0; i < N; i++) {
        c[i] = a[i] + b[i];
        if (c[i] >= k) c[i] -=k;
    }
    return c;
}

mt19937 rnd(42);
map<ehs,int> cnt;
map<int,int> ma;
int a[200010];
ehs num[N];

void slove() {
	cin>>n>>k;
	cnt.clear();ma.clear();
	int id=0;
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		if(!ma[x]) ma[x]=++id;
		a[i]=ma[x];
	}
	ma.clear();
	if(k==1){
		int l=1;
		int ans=0;
		for(int i=1;i<=n;i++){
			while(ma[a[i]]){
				ma[a[l]]--;
				l++;
			}
			ma[a[i]]=1;
			ans+=i-l+1;
		}
		cout<<ans<<endl;
		return ;
	}
//	cout<<"nima"<<endl;
	vector<ehs> pr(n+10);
	for(int i=1;i<=id;i++) for(int j=0;j<N;j++) num[i][j]=rnd()%k;
	int l=0;
	int ans=0;
	pr[0]=num[0]; 
//	if((num[1]^num[1]^num[1])==num[0]) cout<<"ni"<<endl;
//	else cout<<"ma"<<endl;
	for(int i=1;i<=n;i++){
//		cout<<a[i]<<endl;
		pr[i]=num[a[i]]^pr[i-1];
	}
	a[l]=0;
	cnt[pr[0]]++;
	for(int i=1;i<=n;i++){
		ma[a[i]]++;
		while(ma[a[i]]>k+1&&l<i){
			ma[a[l]]--;
			cnt[pr[l]]--;
			l++;
		}
		if(cnt[pr[i]]){
//			cout<<i<<endl;
			ans+=cnt[pr[i]];	
		}
		cnt[pr[i]]++;
	}
	cout<<ans<<endl;
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int T=1;
	cin>>T;
	while (T--) slove();return 0;
}
posted @ 2024-04-23 08:45  MENDAXZ  阅读(165)  评论(0编辑  收藏  举报