集合 hash
lkjzyd20
·
2025-03-31 15:46:13
·
算法·理论
Tags: 字符串 hash
集合哈希
集合与序列的不同点在于,集合是无序的,也就是说,序列要求每个位置一一相等,而集合只需要对应的元素出现次数相等即可。
我们匹配哈希主要是匹配是否出现,而集合哈希主要是匹配出现次数。
https://blog.csdn.net/notonlysuccess/article/details/130959107
集合哈希之异或哈希
AT_abc250_e [ABC250E] Prefix Equality
https://www.luogu.com.cn/problem/AT_abc250_e
我们可以先给每一个值赋一个随机数,然后用 O(n) 的时间复杂度来进行去重和维护两个哈希前缀数组。
比如样例:
5
1 2 3 4 5
1 2 2 4 3
可以对于每一个数赋予一个随机数,处理出来
999 678 234 576 134
999 678 678 576 134
然后我们之间统计异或哈希前缀即可。
#include
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
mt19937 rd(time(0));
const int N=2e5+5;
int n,m,a[N],b[N],f1[N],f2[N];
map
main(){
cin>>n;
rep(i,1,n)cin>>a[i],vis[a[i]]=1;
rep(i,1,n)cin>>b[i],vis[b[i]]=1;
for(auto v:vis)g[v.first]=rd();
rep(i,1,n)a[i]=g[a[i]],b[i]=g[b[i]];
rep(i,1,n){
if(!mpa[a[i]]) f1[i]=f1[i-1]^a[i],mpa[a[i]]=1;
else f1[i]=f1[i-1];
if(!mpb[b[i]]) f2[i]=f2[i-1]^b[i],mpb[b[i]]=1;
else f2[i]=f2[i-1];
}
int Q;cin>>Q;
for(;Q;--Q){
int x,y;cin>>x>>y;
if(f1[x]==f2[y])puts("Yes");
else puts("No");
}
return 0;
}
看到代码,很多人肯定觉得不对呀,这不会有很大的冲突吗?
但是,我们由于先赋予了一个独一无二的随机数,所以它的冲突数将会大大减小。
其实这道题还可以使用加法哈希或乘法哈希,但是按照运算来说,异或哈希使用的常数更小,而且冲突也更小。
CF1175F The Number of Subpermutations
https://www.luogu.com.cn/problem/CF1175F
水紫一道,异或哈希模板。
观察到一个子区间 [l, r] 如果是一个 1 到 r - l +1 的排列,那么等价于:
我们先预处理出 1\sim n 每一个排列的异或哈希,令它为 f,再处理出一个数列的异或哈希,然后再从前往后找每一个 1 出现的位置,又定义一个类似尺取法的 j,每一次向右扩展,取这个区间的最大值,然后使用前缀哈希判断是否是排列即可。
如果是左边的怎么办呢,我们只需要将这个数列倒转过来,然后再执行前面的操作就可以了。
#include
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=300010;
mt19937_64 rd(time(0));
int n,a[N],g[N],pre[N],f[N],ans;
main(){
cin>>n;
rep(i,1,n)cin>>a[i],g[i]=rd(),f[i]=f[i-1]^g[i];
rep(i,1,n)pre[i]=g[a[i]]^pre[i-1];
int j=1;
rep(i,1,n){
if(a[i]==1)j=1,++ans;
else{
j=max(j,a[i]);
if(i>=j&&(pre[i]^pre[i-j])==f[j])++ans;
}
}
reverse(a+1,a+n+1);
rep(i,1,n)pre[i]=g[a[i]]^pre[i-1];
j=1;
rep(i,1,n){
if(a[i]==1)j=1;
else{
j=max(j,a[i]);
if(i>=j&&(pre[i]^pre[i-j])==f[j])++ans;
}
}
cout< return 0; } CF869E The Untended Antiquity 二维异或哈希和二维树状数组板题。 给定 n\times m 的网格,q 次操作: 将第 [x_1,x_2] 行,第 [y_1,y_2] 列的矩形框起来。 删掉第 [x_1,x_2] 行,第 [y_1,y_2] 列的矩形框,保证该矩形框存在。 询问 (x_1,y_1) 和 (x_2,y_2) 是否连通。 思路: 一个很显然的性质,我们查询的两个点只有在围在同样的围墙中才能联通,那我们可以给每个矩形框赋值,然后标记一下,使用二维树状数组维护即可。 但是可能会遇到哈希冲突的情况,所以要使用随机数来减小哈希冲突。 map mt19937 rd(time(0)); int f[N][N]; void add(int x,int y,int v){ for(int i=x;i<=n;i+=i&-i) for(int j=y;j<=m;j+=j&-j) f[i][j]^=v; } int ask(int xx,int yy){ int ans=0; for(int i=xx;i;i-=i&-i) for(int j=yy;j;j-=j&-j) ans^=f[i][j]; return ans; } void change(int xx1,int yy1,int xx2,int yy2,int v){ add(xx1,yy1,v); add(xx2+1,yy1,v); add(xx1,yy2+1,v); add(xx2+1,yy2+1,v); } void solve(){ int opt,xx1,yy1,xx2,yy2; cin>>opt>>xx1>>yy1>>xx2>>yy2; if(opt==1){ vis[{xx1,yy1,xx2,yy2}]=rd(); change(xx1,yy1,xx2,yy2,vis[{xx1,yy1,xx2,yy2}]); } else if(opt==2){ change(xx1,yy1,xx2,yy2,vis[{xx1,yy1,xx2,yy2}]); } else { int fx=ask(xx1,yy1),fy=ask(xx2,yy2); puts(fx==fy?"Yes":"No"); } } 集合哈希之 sum 哈希 P8819 [CSP-S 2022] 星战 有一个 n 个点 m 条有向边的图,每条边有可用和不可用两个状态,初始时均为可用边。有 q 个操作: 操作 \tt1:让一条可用边变为不可用边 操作 \tt2:让所有指向这个点的边中,可用边变为不可用边 操作 \tt3:让一条不可用边变为可用边 操作 \tt4:让所有指向这个点的边中,不可用边变为可用边 每次操作之后,判断是否同时满足 条件 \tt1:所有的点出度均为 \tt1。 条件 \tt2:所有的点都满足从该点出发可以走到一个环中。 思路:首先很明显,条件 \tt2 是没有用的,因为你随便构造一个符合条件 \tt1 图,你会发现它满足条件 \tt1。 当然你还可以证明一下,其实这是一棵内向基环树,oi-wiki 上给了详细的定义,(反正你考场上也证明不出,还不如直接随便画几个易得)。 然后 \tt 1,3 是可以直接 O(1) 解决的,\tt 2,4 可以暴力,这就有 50 分了(直接输出 NO 45 分)。 我们现在主要来优化 \tt 2,4 操作,我们来维护所有的点它们的出度和,对于每一个点,它们的出度我们给它赋予一个随机值,然后对于每一次操作,直接异或哈希,判断是否原本所有的出度和即可。 这题我使用和异或哈希差不多的 sum 哈希,不知道为什么我的异或哈希过不去。 #include #define int long long #define rep(i, l, r) for(int i = l; i <= r; ++ i) #define per(i, r, l) for(int i = r; i >= l; -- i) using namespace std; const int N=5e5+10; int n,m,q,a[N]; int tot,ans; // 定义 to_i.first 表示 i 点出度和,to_i.second 表示最开始 i 点出度和 pair mt19937 rd(time(0)); void solve(){ int opt,u,v; cin>>opt; if(opt==1){ cin>>u>>v; to[v].first-=a[u];//删除 v-u 的出度 tot-=a[u];//总出度减去 v-u 的出度 } else if(opt==2){ cin>>u; tot-=to[u].first;//总出度减去 u 的出度 to[u].first=0;//u 的出度清零 } else if(opt==3){ cin>>u>>v; to[v].first+=a[u];//同理 tot+=a[u]; } else{ cin>>u; tot+=to[u].second-to[u].first;//总出度加上 u 最开始的出度减去现在的出度 to[u].first=to[u].second;//u 的出度更新为最开始的出度 } puts(tot==ans?"YES":"NO"); } main(){ cin>>n>>m; rep(i,1,n)a[i]=rd(),ans+=a[i];// 对于每一个点赋随机值 rep(i,1,m){ int u,v; cin>>u>>v; to[v].first+=a[u]; //v 点加上 u-v 点的出度 to[v].second=to[v].first; tot+=a[u]; // 总出度更新 } int Q;cin>>Q; for(;Q;--Q)solve(); return 0; }