关于异或哈希
Re:疑惑异或哈希
头图
狂三~❤ 嘿嘿嘿 我的狂三❤
异或哈希是个很神奇的算法,利用了异或操作的特殊性和哈希降低冲突的原理,可以用于快速找到一个组合是否出现、序列中的数是否出现了
算法如其名,异或+哈希。
想起某首歌叫PPAP?
I have an
(Uhh~)
😅
异或
最基本的,也很重要的
考虑到异或运算满足交换律,即
因此组合的不同排列的异或值相等
这样我们便可以忽略顺序,直接统计组合出现次数。
哈希
由于二进制位数有限,会存在一些情况使得异或并不正确,如:
因此需要将原始数据哈希为较大的数降低冲突概率
应用
组合问题
直接来看道例题。
不会有人看不懂英文还不会拿软件翻译吧 虽然翻译软件翻译的跟构式一样
直接沿用异或哈希的思路,不难写出
int n,m;
mt19937_64 rnd(time(0));
unsigned long long code[N],chk[N],Xor[N],a[N];
signed main(){
n=rd;
for(int i=1;i<=n;i++){
a[i]=rd;
}
for(int i=1;i<=n;i++){
code[i]=rnd();
chk[i]=chk[i-1]^code[i];
}
for(int i=1;i<=n;i++){
Xor[i]=Xor[i-1]^code[a[i]];
}
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if((Xor[i-1]^Xor[j])==chk[j-i+1]){
++cnt;
}
}
}
printf("%lld",cnt);
return Elaina;
}
兴高采烈地交上去...乂~T了。
一看数据范围:
好家伙 水 过不去了。
进一步考虑到:
- 满足条件的区间肯定有
和 等于区间长度的最大值
考虑分别向右&左做两次扩展:
设
当
否则往回捯饬
int n,m;
int max(int a,int b){
return a>b?a:b;
}
mt19937_64 rnd(time(0));
unsigned long long code[N],chk[N],Xor[N],a[N];
signed main(){
n=rd;
for(int i=1;i<=n;i++){
a[i]=rd;
}
for(int i=1;i<=n;i++){
code[i]=rnd();
chk[i]=chk[i-1]^code[i];
}
for(int i=1;i<=n;i++){
Xor[i]=Xor[i-1]^code[a[i]];
}
int cnt=0,pos=-1,cnt1=0,mx=0;
for(int i=1;i<=n;i++){
if(a[i]==1){
pos=i,++cnt1,mx=1;
}else if(pos!=-1){
mx=max(mx,a[i]);
if(i-mx+1<=pos)
if((Xor[i]^Xor[i-mx])==chk[mx])
++cnt;
}
}
pos=-1;
for(int i=n;i>=1;i--){
if(a[i]==1){
pos=i,++cnt1,mx=1;
}else if(pos!=-1){
mx=max(mx,a[i]);
if(i+mx>=pos)
if((Xor[i+mx-1]^Xor[i-1])==chk[mx])
++cnt;
}
}
printf("%lld",cnt+cnt1/2);
return Elaina;
}
实际上反过来的时候可以直接reverse一下数组,然后再跑一遍,就不用复制粘贴打两遍了。。。
然后就 AC 了喵~
出现次数问题
二进制的异或的本质是对每一位进行不进位的加法,也就是每一位相加对2取模,即:
假设有一种运算
其实也就是
扩展到
ull xork(ull a,ull b,int k){
vector<int> vec;
while(a||b){
vec.push_back((a+b)%k);
a/=k;
b/=k;
}
ull res=0;
ull p=1;
for(auto x:vec){
res+=p*x;
p*=k;
}
return res;
}
ull xork(ull x,ull y,int k){
int sum=0,p=1;
// if(y%2==0) swap(x,y);
while(x||y){
sum+=(x%k+y%k)%k*p;
p*=k;
x/=k;
y/=k;
}
return sum;
}
我们还是来看道例题
其实上一道题也可以在洛谷找到对应的翻译版本 (逃
首先我们知道一个思想,证明充要条件就要证明它既充分又必要;同样,要证明一个数等于某个值,必须让它既小于等于又大于等于这个值。
迁移到本题,我们让所有数的出现个数
第一个约束随便糊一个异或哈希即可。
关键在于第二个约束。
我们考虑使用类似于双指针的算法:
考虑对于一个满足约束二的
让新加入的右指针的值
令
当删除完毕之后,我们统计满足 map
或者哈希表完成。
那么这道题就完成了,复杂度
然后关于...算了,自己看吧。
猫娘触发关键词被人举报删了 (悲
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define ull unsigned long long
#define rd read()
#define mkp make_pair
#define psb push_back
#define Elaina 0
inline ll read(){
ll f=1,x=0;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
return f*x;
}
const int mod=1e9+7;
const int N=1e6+100;
const int inf=0x7fffffff;
int n,k;
mt19937_64 rnd(time(0));
ull code[N],pre[N],a[N],num[N];
map<ull,ull> mp;
ull xork(ull x,ull y,int k){
ull sum=0,p=1;
while(x||y){
sum+=(x%k+y%k)%k*p;
p*=k;
x/=k;
y/=k;
}
return sum;
}
signed main(){
srand(time(0));
n=rd,k=3;
for(int i=1;i<=n;i++){
a[i]=rd;
}
for(int i=1;i<=N;i++){
code[i]=rnd()%(1ll<<63);
}
for(int i=1;i<=n;i++){
pre[i]=xork(pre[i-1],code[a[i]],k);
}
int cnt=0;
mp[0]=1;
for(int l=0,r=1;r<=n;++r){
++num[a[r]];
while(num[a[r]]>3){
--num[a[l]];
if(l>0) --mp[pre[l-1]];
++l;
}
cnt+=mp[pre[r]];
++mp[pre[r]];
}
printf("%lld",cnt);
return Elaina;
}
问题
判断一个序列的乘积是否是
( 为某个整数)
这个就比较好说了,根据唯一分解定理
其中,
则
那么当这个序列质因子出现的次数都是偶数次即可。
练习
这里给的都是vjudge的链接。
大人~ 制作不易喵~ 赏个赞吧喵~
蒂蒂~❤ 嘿嘿嘿 我的蒂蒂❤
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)