题目
长度为n(n<=3e5)的数组,q(q<=3e5)次操作,操作分两种,
1. 输入i x(x<=1e9),将ai改成x
2. 输入l r k(1<=k<=n),询问对于在[l,r]内出现的每种数,其在[l,r]内的出现次数是不是均为k的倍数
思路来源
gzchenben的讲解
jjleo的代码
题解
随机化哈希,人尽皆知典中典
k的倍数,区间每种数的出现次数很难维护,考虑概率&哈希
对于输入的ai,先将其哈希映射到另一个数上,然后考虑按二进制位拆,
如果对于二进制的每一位,其在[l,r]内的和都是k的倍数,我们就认为答案是Yes,
这样错误的概率,对于某一位来说,在k=2时,是1/2,
因为随机哈希,可能将两个二进制这一位原本不同的数,映射成相同了,
但是,控制位数足够多后,这样询问出错的概率就很低了,
考虑到3e5组样例,实际后台可能有100个case,3e8左右,所以30位≈1e9不一定行,
这里控制位数M在35-40左右即可
不被hack掉的tips
1. 哈希是完全随机的(如mt19937_64)
2. 对于不同的ai,哈希出来的数不重复
心得
mt19937_64用起来很方便,实际控制M=64保险,但M=35-40就够用了,1/(2^35)的概率很低了
放一个抄来的随机数生成的方法,主要这题需要尽可能保证每次gen的随机数不重复
update:unordered_map被卡了,赶紧改回map
int gen(){
static int o=4354347;
o=(1ll*o*19260817+23333333)%998244353+1;
return o;
}
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10,M=40;
int n,q,a[N],op,x,y,l,r,k;
map<int,ll>to;
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
ll gen(){
return rng()&((1ll<<40)-1);
}
struct BIT{
int n,tr[N];
void init(int _n){
n=_n;
memset(tr,0,sizeof tr);
}
void add(int x,int v){
for(int i=x;i<=n;i+=i&-i)
tr[i]+=v;
}
int sum(int x){
int ans=0;
for(int i=x;i;i-=i&-i)
ans+=tr[i];
return ans;
}
}tr[M];
int main(){
scanf("%d%d",&n,&q);
for(int j=0;j<M;++j){
tr[j].init(n);
}
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(!to.count(a[i])){
to[a[i]]=gen();
}
int v=to[a[i]];
for(int j=0;j<M;++j){
if(v>>j&1)tr[j].add(i,1);
}
}
for(int i=1;i<=q;++i){
scanf("%d",&op);
if(op==1){
scanf("%d%d",&x,&y);
if(!to.count(y)){
to[y]=gen();
}
int p=to[a[x]],v=to[y];
for(int j=0;j<M;++j){
int w=(v>>j&1)-(p>>j&1);
if(w)tr[j].add(x,w);
}
a[x]=y;
}
else{
scanf("%d%d%d",&l,&r,&k);
bool ok=1;
for(int j=0;j<M && ok;++j){
ok&=((tr[j].sum(r)-tr[j].sum(l-1))%k==0);
}
puts(ok?"YES":"NO");
}
}
return 0;
}