分块法 CF551E GukiZ and GukiZiana

前段时间看的莫队算法,对分块法已经有所了解

这次做完了这道题,感觉分块法就是逆天....


题意:

两种操作

1 l r x表示区间[l,r]里的数都增加x

2 y表示在区间[1,n]中寻找等于y的最大下标j和最小下标i,答案就是j-i,如果不存在就输出-1


思路:

把n个数分成sqrt(n)块,维护每一个块内部的数值单调递增

每次1操作,设l在L块,r在R块,那么L+1和R-1的所有块的懒惰标记col全部加上y,L和R块的,只在原下标在[l,r]范围内的加上y,并且最后再次排序,总是使每个块内部单调递增

每次2操作,因为每个块内部都是单调递增的,那么可以直接二分第x-col[i]个区间,col[i]表示这个块每个数已经都被加了多少


代码写的比较怂......

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PII;
const int MX=500000+5;
#define Val first
#define Pid second

PII A[MX];
LL col[MX];
int n,m,unit;

void build(){
    memset(col,0,sizeof(col));
    for(int i=1;i<=unit;i++){
        int l=i*i,r=min((i+1)*(i+1)-1,n);
        sort(A+l,A+r+1);
    }
}

void update(int l,int r,LL x){
    int L=sqrt(l+0.5),R=sqrt(r+0.5),ll,rr;

    for(int i=L+1;i<=R-1;i++){
        col[i]+=x;
    }

    ll=L*L;rr=min((L+1)*(L+1)-1,n);
    for(int i=ll;i<=rr;i++){
        if(l<=A[i].Pid&&A[i].Pid<=r) A[i].Val+=x;
    }
    sort(A+ll,A+rr+1);

    if(L==R) return;

    ll=R*R;rr=min((R+1)*(R+1)-1,n);
    for(int i=ll;i<=rr;i++){
        if(l<=A[i].Pid&&A[i].Pid<=r) A[i].Val+=x;
    }
    sort(A+ll,A+rr+1);
}

int query(LL x){
    int p1,p2,ans1=-1,ans2=-1;

    for(int i=1;i<=unit;i++){
        int l=i*i,r=min((i+1)*(i+1)-1,n);

        p1=lower_bound(A+l,A+r+1,make_pair(x-col[i],0))-A;
        if(p1!=r+1&&A[p1].Val==x-col[i]){
            ans1=A[p1].Pid;
            break;
        }
    }

    for(int i=unit;i>=1;i--){
        int l=i*i,r=min((i+1)*(i+1)-1,n);

        p2=upper_bound(A+l,A+r+1,make_pair(x-col[i],n+1))-A-1;
        if(p2!=0&&A[p2].Val==x-col[i]){
            ans2=A[p2].Pid;
            break;
        }
    }

    if(ans1==-1||ans2==-1) return -1;
    return ans2-ans1;
}

int main(){
    scanf("%d%d",&n,&m);

    unit=sqrt(n+0.5);
    for(int i=1;i<=n;i++){
        LL t;
        scanf("%I64d",&t);
        A[i]=make_pair(t,i);
    }

    build();

    while(m--){
        int q,l,r;LL x;
        scanf("%d",&q);
        if(q==1){
            scanf("%d%d%I64d",&l,&r,&x);
            update(l,r,x);
        }else{
            scanf("%I64d",&x);
            printf("%d\n",query(x));
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值