解题报告-和哈希构造近似充要条件
和哈希
有一种哈希方式叫做和哈希,其本质思想是将一个必要不充分条件近似转换成充要条件,并利用此条件进行答案的求解。
例 1. 杂题
题意:给定一个序列与 次询问,每次判定一个区间 内是否存在出现次数为奇数的数字。
记命题 “区间 中存在出现次数为奇数的数字” 为 。
思路:考虑异或的性质:奇数个 异或和 ,偶数个 异或和 。
所以 成立的必要条件之一是“ 内所有数的异或和不为 ”。
我们可以近似的认为,若 “该区间内所有数字的异或和为 ”,则 成立。
但如果单纯这么算可能会被卡。
考虑对于每种数字赋一个随机权值,并以此随机权值进行异或计算。
可以发现此时出现错误判断的概率极低。
并且区间异或和可以方便的使用前缀异或和维护。
复杂度 。
例 2. 相似序列
定义两个序列 相似,当且仅当 长度相同并且排序后 序列中至多有一个位置的数字不同。
给定一个序列与 次询问,每次询问形如 ,求 构成的序列是否与 相似。
保证两段区间长度相等。
以下记 。
定义不和谐度为序列 分别排序后两者对应位置数字不同的位置个数。
算法一()
暴力排序判断。
。
算法二()
注意到一个性质:
-
如果两个区间的所有数字完全相同,则一定相似。
-
如果两个区间有且仅有一个位置不同,则设 中此位置的数字在 去重后排名第 小,则 中此位置的数字在 去重后排名第 或 。通俗的说:设 中此位置数字为 , 中此位置数字为 ,则 中去重后 与 中间没有数字。
证明:
第一条显然,这里证明第二条。
反证法,假设数字为 ,则作图:
则如果 在 中变成 :
发现此时 整体右移一位,所以不和谐度至少增加 。
而此时 已经对不和谐度产生了 的贡献,所以修改后一定不合法。
证毕。
综上,可以开 个桶来求解区间某个数字的个数,分别找出最小的、最大的区间个数不相同的数字,再判断区间内是否存在在他们值域之间的数字。
复杂度 , 为值域。
const int N=1e5+10;
int cnt[110][N];
int n,q;
main() {
cin>>n>>q;
FOR(i,1,n) {
int x=read();
FOR(j,1,100) cnt[j][i]=cnt[j][i-1];
++cnt[x][i];
}
while(q--) {
int l1=read(),r1=read(),l2=read(),r2=read();
int mn=1e9,mx=0;
FOR(i,1,100) if(cnt[i][r1]-cnt[i][l1-1]!=cnt[i][r2]-cnt[i][l2-1]) cmin(mn,i),cmax(mx,i);
if(!mx) {
puts("YES");
continue;
}
int pk=0;
FOR(i,mn+1,mx-1) {
if(max(cnt[i][r1]-cnt[i][l1-1],cnt[i][r2]-cnt[i][l2-1])) {
puts("NO");
pk=1;break;
}
}
if(pk) continue;
if(abs((cnt[mx][r1]-cnt[mx][l1-1])-(cnt[mx][r2]-cnt[mx][l2-1]))>1) puts("NO");
else puts("YES");
}
return 0;
}
算法三(正解)
注意到这玩意本质上是判断桶是否相同。
可以使用主席树维护桶。
那么找最大/最小的桶内不相同的值可以使用线段树上二分或者二分+线段树。
至于如何判断值域 是否相同,使用和哈希,对值域每个数字单独随机一个权值,这样主席树只需要维护区间和,并可以方便的使用作差维护两个版本作差后的主席树。
。
const int N=1e5+10,P=131;
int n,q,root[N],tot,num[N];
ULL p[N];
mt19937 rnd(time(0));
struct Node {int ls,rs;ULL H,siz;}t[N*40];
int Build(int nd) {return t[++tot]=t[nd],tot;}
int Insert(int p,int l,int r,int x) {
p=Build(p);t[p].H+=num[x],t[p].siz++;
if(l==r) return p;int mid=l+r>>1;
x<=mid?t[p].ls=Insert(t[p].ls,l,mid,x):t[p].rs=Insert(t[p].rs,mid+1,r,x);return p;
}
ULL Ask(int p,int q,int ql,int qr,int l,int r) {
if(ql>qr) return 0;
if(ql<=l&&r<=qr) return t[p].H-t[q].H;
int mid=l+r>>1;ULL S=0;
if(ql<=mid) S+=Ask(t[p].ls,t[q].ls,ql,qr,l,mid);
if(qr>mid) S+=Ask(t[p].rs,t[q].rs,ql,qr,mid+1,r);
return S;
}
int Cnt(int p,int q,int l,int r,int x) {
if(l==r) return t[p].siz-t[q].siz;
int mid=l+r>>1;return x<=mid?Cnt(t[p].ls,t[q].ls,l,mid,x):Cnt(t[p].rs,t[q].rs,mid+1,r,x);
}
main() {
cin>>n>>q;
int V=1e5;FOR(i,1,V) num[i]=rnd()%1000000000;
p[0]=1;FOR(i,1,V) p[i]=p[i-1]*P;
FOR(i,1,n) root[i]=Insert(root[i-1],1,V,read());
while(q--) {
int l1=read(),r1=read(),l2=read(),r2=read();
if(Ask(root[r1],root[l1-1],1,V,1,V)==Ask(root[r2],root[l2-1],1,V,1,V)) {puts("YES");continue;}
int l=1,r=V,mid,posl=0;
while(mid=l+r>>1,l<=r) Ask(root[r1],root[l1-1],1,mid,1,V)!=Ask(root[r2],root[l2-1],1,mid,1,V)?posl=mid,r=mid-1:l=mid+1;
l=1,r=V;int posr=V+1;
while(mid=l+r>>1,l<=r) Ask(root[r1],root[l1-1],mid,V,1,V)!=Ask(root[r2],root[l2-1],mid,V,1,V)?posr=mid,l=mid+1:r=mid-1;
if(!Ask(root[r1],root[l1-1],posl+1,posr-1,1,V)&&!Ask(root[r2],root[l2-1],posl+1,posr-1,1,V)&&abs(Cnt(root[r1],root[l1-1],1,V,posl)-Cnt(root[r2],root[l2-1],1,V,posl))<=1) puts("YES");
else puts("NO");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具