「LOJ #6500」「雅礼集训 2018 Day2」操作
阅读原文时间:2023年07月08日阅读:1

description

LOJ 6500

solution

根据常有套路,容易想到将区间差分转化为异或数组上的单点修改,即令\(b_i=a_i \ xor\ a_{i-1}\),

那么将\([l,l+k-1]\)取反,就相当于将\(b[l]\)与\(b[l+k]\)取反,若\(b[l]\)与\(b[l+k]\)都是1,等于是二者消掉了

于是发现一次操作只会对\(mod k\)余数相同的位置造成影响,并且每次操作只能消去两个1,

故区间\([l,r]\)的\(b\)数组能全部变成0当且仅当这段区间内的所有位置按\(mod k\)的余数分组后,每组中都有偶数个1

处理这样的情况有一个常见套路,那就是给\(mod k\)的每个余数分配一个随机数哈希值,那么一段区间中若\(mod k=r\)的位置中有偶数个1,那么异或起来就会变成0

故区间\([l,r]\)的\(b\)数组能全部变成0当且仅当这段区间内的异或和为0

考虑如何求操作次数,容易想到将最优方案是对于每个剩余系,将相邻的2个1配对

于是对于一个\(mod k=r\)的剩余系,设剩余系内所有位置从小到大分别为\(a_1,a_2,⋯,a_{2k−1},a_{2k}\),那么答案就是\(\frac{(a_2−a_1)+(a_4−a_3)+⋯+(a_{2k}−a_{2k−1})}{k}\)。

我们可以预处理这个式子的前缀和,因为从左至右依次处理时,每一个剩余系内从右至左的第奇数个位置有正的贡献,第偶数个有负的贡献,于是新加入一个\(i\)就会导致之前的贡献全部取反再加上\(i\)的贡献

于是再特殊处理一下边界就行了

code

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=2e6+10;
int n,k,m,a[N],dis[N],d[N],L[N],R[N];
ull hsh[N],sum[N];
char s[N];
int main(){
    scanf("%d%d%d",&n,&k,&m);
    scanf("%s",s+1);
    for(int i=0;i<k;++i) hsh[i]=rand()*rand();
    for(int i=1;i<=n;++i){
        a[i]=s[i]-'0';
        sum[i]=sum[i-1];dis[i]=dis[i-1];
        if(a[i]^a[i-1]){
            sum[i]^=hsh[i%k];
            dis[i]+=-(d[i%k]<<1)+i;
            d[i%k]=i-d[i%k];
        }
        L[i]=d[i%k];
        R[i]=d[(i+1)%k];
    }
    for(int i=1,l,r;i<=m;++i){
        scanf("%d%d",&l,&r);
        ull hsht=sum[l]^sum[r]^(a[l]*hsh[l%k])^(a[r]*hsh[(r+1)%k]);
        if(hsht!=0) puts("-1");
        else{
            int ret=dis[r]-dis[l];
            if(a[l]==1) ret-=l-(L[l]<<1);
            if(a[r]==1) ret+=r+1-(R[r]<<1);
            printf("%d\n",ret/k);
        }
    }
    return 0;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章