队列分块

水博客!

数列分块入门2:

https://loj.ac/problem/6278

给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值x 的元素个数。

题外话:

这玩意坑了我一个上午,woc我看了看题目是求小于,我一直求的是小于等于x的元素个数

思路:

首先分析一下题目中的性质,如果a<b,那么对于任意c>=0都有a+c<b+c,
所以add操作如果覆盖了一整个块,那么其实在这一个块内的顺序是不会变的,所以只用处理旁边的没有覆盖一整块的区间
也就是零散的区间,就暴力处理,然后添加完对一整个块重新排序,查询操作就比较简单了,因为每一个块都是有序的,所以可以在整块内二分
如果是零散的区间,就直接暴力查询。
先给每个块来个排序(块内排序),总时间复杂度O(nlogn)
然后要记录下块里面每个块在原来对应的位置,这个时间复杂度几乎忽略不计(可以开结构体)
1.对于每一个查询,
1.1零散的不足一块的我就直接暴力查询有多少个比当前查询值要小的,
1.2对于整块的我就二分(因为是排好序的)查询一下有多少个比当前查询值小的
2.对于每一个修改,
2.1零散不足一块的我就遍历这个块,如果这个点的原编号在修改区间内就修改最后重新对这一块排序就行了
2.2对于满足一块的就直接维护add数组

综上,总时间复杂度为:O(n*sqrt(n)*log(sqrt(n))),n<=50000,很明显sqrt(n)不超过240,log(sqrt(n))不超过8,50000*240*8=9.6*10^7,这是最坏情况(也能跑过,如果你RP不那么低),
也就是n次操作全是查询而且每次查询都是1到n,但是实际跑出来的复杂度要远低于这个值的,所以,大胆暴力!!!
 
复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long 
int n,m,t=0;
struct node{int data,id;}r[200005];
int L[100005],R[100005],block,pos[200005],add[200005];
int cmp(node A,node B){return A.data<B.data;}

int change(int x,int y,int k);
int query(int x,int y,int k);
signed main(){
    cin>>n;m=n;
    for (int i = 1 ; i <= n ; i ++)cin>>r[i].data,r[i].id=i;
    block=(int)(sqrt(n));
    while(R[t] != n){
        t++,L[t]=(t-1)*block+1;
        R[t]=min(t*block,n);//确定每一个块的边界
    }
    for (int i = 1 ; i <= t ; i ++){
        for (int j = L[i] ; j <= R[i] ; j ++)
        pos[j]=i,add[i]=0;
        sort(r+L[i],r+R[i]+1,cmp);//排序每一个块,这个加1一定要留,不然会Wa,我也不知道为什么
    }
    for (int i = 1 ; i <= m ; i ++){
        int op,x,y,k;
        cin>>op>>x>>y>>k;
        if(op == 0)change(x,y,k);
        else cout<<query(x,y,k*k)<<endl;
    }
    return 0;
}
int find(int x,int y,int num,int ttt){//二分查询,x是左端点,y是右端点,num是要查询的那个标准值,ttt是当前块的编号
    int s=x,mm=y;
    while(x < y){
        int mid=(x+y)>>1;
        if(r[mid].data+add[ttt] < num)x=mid+1;
        else y=mid-1;
    }
    while(r[x].data + add[ttt] >= num && x>=s)x--;//防止我二分爆炸
    if(x > mm)x=mm;
    return x-s+1;
}
int query(int x,int y,int k){
    int p=pos[x],q=pos[y];
    if(pos[x] == pos[y]){
        int total=0;
        for (int i = L[p] ; i <= R[p] ; i ++)
        if(x <=r[i].id&&r[i].id <= y && r[i].data+add[p] < k)
        total++;//暴力统计
        return total;
    }
    else {
        int total=0;
        for (int i = L[p] ; i <= R[p] ; i ++)
        if(x <=r[i].id && r[i].id  <= R[p] && r[i].data+add[p] < k)
        total++;
        for (int i = L[q] ; i <= R[q] ; i ++)
        if(L[q] <=r[i].id && r[i].id  <= y && r[i].data+add[q] < k)
        total++;//暴力统计
        for (int i = p +1 ; i <= q -1 ; i ++)
        total+=find(L[i],R[i],k,i);//直接二分查找当前块内的小于x的数
        return total;
    }
}
int change(int x,int y,int k){
    int p=pos[x],q=pos[y];
    if(pos[x] == pos[y]){
        for (int i = L[p]; i <= R[p] ; i ++)
        if(x <= r[i].id && r[i].id  <= y)r[i].data+=k;
        sort(r+L[p],r+R[p]+1,cmp);//如果是小块就直接修改
    //因为不是整段都被覆盖,所以有一部分是被添加了,而又有一部分没有被加到,所以要重新排序,防止失序 } else { for (int i = L[p]; i <= R[p] ; i ++) if(x <= r[i].id && r[i].id <= R[p])r[i].data+=k; sort(r+L[p],r+R[p]+1,cmp); for (int i = L[q]; i <= R[q] ; i ++) if(L[q] <= r[i].id && r[i].id <= y)r[i].data+=k; sort(r+L[q],r+R[q]+1,cmp);//每次处理零散的块都要排序 for (int i = p + 1; i <= q - 1 ; i ++) add[i]+=k;//整段直接维护就好了 } return 0; }
复制代码
原文地址:https://www.cnblogs.com/MYCui/p/13773852.html