Codeforces213 E. Two Permutations(线段树维护hash值)

题意:

给定长度为n的排列a,和长度为m的排列b,
问有多少种d,满足对a中的所有元素+d后,a是b的子序列

数据范围:1<=n<=m<=2e5

解法:

考虑枚举d,判断a(i)+d是否是b的子序列

当d=0的时候,就是将b中的1到n提取出来,判断取出的数的相对位置是否和排列a一致
当d=1的时候,就是将b中的2到n-1提取出来,判断取出的数的相对位置是否和排列a(i)+1一致

因为需要快速比较一致性,考虑hash。
先计算出排列a的hash值,本题采用的hash方式:
hash_A=a(1)p0+a(1)p1+…a(n)pn-1

那么如何计算b中1到n子序列的hash值呢?
可以令pos(b(i))=i,将pos(1)=1到pos(n)=n放入线段树对应的pos中,让线段树维护这个序列的hash值

一开始让我很疑惑的点:排列b的子序列可能是不连续的,那么中间会出现空节点,那base不就歪了吗?
仔细查看了别人的代码发现是新开了一个信息标记Sum,用来记录节点区间有多少个非空叶子节点

区间节点合并的时候,根据右节点非空叶子数量来进行便宜,而不是平常的按下标固定偏移,学到了。
这样就相当于跳过了空的位置,计算出的hash值就是正确的hash值。
pushup代码:

void pushup(int node){
    Sum[node]=Sum[node*2]+Sum[node*2+1];
    A[node]=A[node*2]*base[Sum[node*2+1]]+A[node*2+1];
}

当d=1的时候,查询的是2到n+1子序列的hash值,那么将pos(1)=1从线段树删除,然后将pos(n+1)=n加入。
因为排列a变成了a(i)+1,每个数位都增加了1,hash值增加了p0+p1+…pn-1,这个就是base的前缀和。

枚举d,重复进行上述操作即可。

code:
#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int maxm=2e5+5;
const int p=1331;
int a[maxm];
int b[maxm];
int pos[maxm];
ull base[maxm];
ull temp;
ull sum;
int n,m;
//
ull A[maxm<<2];
int Sum[maxm<<2];
void pushup(int node){
    Sum[node]=Sum[node*2]+Sum[node*2+1];
    A[node]=A[node*2]*base[Sum[node*2+1]]+A[node*2+1];
}
void update(int x,int val,int l,int r,int node){
    if(l==r){
        Sum[node]=(val!=0);
        A[node]=val;
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid)update(x,val,l,mid,node*2);
    else update(x,val,mid+1,r,node*2+1);
    pushup(node);
}
//
signed main(){
    ios::sync_with_stdio(0);
    cin>>n>>m;
    base[0]=1;
    for(int i=1;i<=n;i++){
        base[i]=base[i-1]*p;//base
        sum+=base[i-1];//base前缀和,一次移动的总增量
    }
    for(int i=1;i<=n;i++){
        cin>>a[i];
        temp=temp*p+a[i];//排列a的hash值
    }
    for(int i=1;i<=m;i++){
        cin>>b[i];
        pos[b[i]]=i;
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        update(pos[i],i,1,m,1);
    }
    if(A[1]==temp){
        ans++;
    }
    for(int i=n+1;i<=m;i++){
        update(pos[i-n],0,1,m,1);
        update(pos[i],i,1,m,1);
        if(A[1]==sum*(i-n)+temp){
            ans++;
        }
    }
    cout<<ans<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值