给定了一个长度不超过5e5的序列,0<=a[i]<65 536,q次操作,q不超过5e5。
操作1:[l,r]区间每个数加1,并且对65536取模
操作2:给出x,y,L,询问[x,x+l-1]
,[y,y+l-1]
两个区间的序列是否相等。
这题区间修改是每次加1,一共最多加nq,所以我们取模可以写成单点修改,
最多取模(n*q)/65536 次,(通过维护区间最大值剪枝实现这个暴力的单点修改。)
所以我们可以先不考虑取模,发现这题是可以用hash+线段树写的。
- 假设hash进制为base,令
p[i]=base^i
。我们令序列第i个位置的权值为p[i],用线段树维护区间权值和。[l,r]区间的权值和就是p[l]+p[l+1]...p[r]
. 同时线段树维护区间hash值之和。 - 区间加1实质就是区间hash值加上区间权值。
- 查询
[x,x+l-1] ,[y,y+l-1]
,不妨假设x<y,我们先查这两个区间的hash值t1,t2,然后t1*p[y-x]与t2相比,相等则说明序列相等。
总结一下:
通过赋予区间一个权值来实现hash,线段树维护区间hash值之和。
取模操作可以通过维护最大值+单点修改实现,每次区间修改完毕就去进行取模操作。
注:此题卡自然溢出,直接用ull会wa test12,模数要选别的比如19260827。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 5e5 + 10;
const int mod = 19260817;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
const int base = 13331;
ll inv(ll b){while(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){while(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct node
{
#define lc rt<<1
#define rc rt<<1|1
int l,r;
int m;//区间最大值
ull bs,hash;//bs代表区间权值 hash是区间hash值
}tree[maxn<<2];
int tag[maxn<<2];
ull p[maxn];//base^i
int n,q;
inline void pushup(int rt)
{
tree[rt].hash = (tree[lc].hash + tree[rc].hash)%mod;
tree[rt].m = max(tree[lc].m , tree[rc].m);
}
void build(int rt,int l,int r)
{
tree[rt].l=l,tree[rt].r=r;
if(l==r)
{
scanf("%d",&tree[rt].m);
tree[rt].bs=p[l];//理解区间权值的含义 idx==i处权值为p[i]
tree[rt].hash=p[l]*tree[rt].m % mod;
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
tree[rt].bs=(tree[lc].bs + tree[rc].bs)%mod;
pushup(rt);
}
inline void change(int rt,int v)//区间+v
{
tree[rt].hash+=(tree[rt].bs*v)%mod;
tree[rt].hash %= mod;
tree[rt].m += v;//先不考虑取模操作
tag[rt]+=v;
}
inline void pd(int rt)
{
if(tag[rt])
{
change(lc,tag[rt]);
change(rc,tag[rt]);
tag[rt]=0;
}
}
inline void upd(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(l>vr || r<vl) return ;
if(vl<=l && r<=vr)
{
change(rt,1);
return ;
}
pd(rt);
upd(lc,vl,vr);upd(rc,vl,vr);
pushup(rt);
}
inline void upd_m(int rt)//取模操作 从树根递归+剪枝 单点修改
{
if(tree[rt].m < 65536) return ;
int l=tree[rt].l,r=tree[rt].r;
if(l==r)
{
tree[rt].m %= 65536;
tree[rt].hash = tree[rt].m*tree[rt].bs % mod;
return ;
}
pd(rt);
upd_m(lc);upd_m(rc);
pushup(rt);
}
inline ull qry(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(l>vr || r<vl) return 0;
if(vl<=l && r<=vr) return tree[rt].hash;
pd(rt);
int mid=l+r>>1;
if(vr<=mid) return qry(lc,vl,vr);
else if(vl>mid) return qry(rc,vl,vr);
return (qry(lc,vl,vr)+ qry(rc,vl,vr))%mod;
}
int main()
{
p[0]=1;
for(int i=1;i<maxn;i++) p[i]=p[i-1]*base % mod;
scanf("%d %d",&n,&q);
build(1,1,n);
int f,x,y,l;
while(q--)
{
f=read(),x=read(),y=read();
if(f==2)
{
l=read();
if(x>y) swap(x,y);
ull t1=qry(1,x,x+l-1);
ull t2=qry(1,y,y+l-1);
t1=t1*p[y-x]%mod;
if(t1==t2) puts("yes");
else puts("no");
}
else
{
upd(1,x,y);
upd_m(1);//每次区间修改完 都检测一下取模
}
}
return 0;
}
很久之前也写过一道线段树维护区间hash值的题,但是和这题的处理方法完全不同。传送门这题的修改只涉及单点修改,所以我们区间没有赋予一个权值,而是利用hash的区间合并进行pushup的,即:
,这种写法下,区间查询得到的就是一个真实的字符串hash值。