2020ccpc威海 G.Caesar Cipher (hash+线段树)

传送门

给定了一个长度不超过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的,即:
2020ccpc威海 G.Caesar Cipher (hash+线段树)

,这种写法下,区间查询得到的就是一个真实的字符串hash值。

上一篇:3 -【 API 开放平台安全设计 】- 3 接口安全加密传输


下一篇:OpenCV 加载caffe 模型进行推理