LOJ #6279. 数列分块入门 3

原题地址:https://loj.ac/problem/6279
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)。
思路:和区间加法和分块2的差不多。我们只需要对分块2的查询操作做修改就行了。

注意:除了可以像我下面的代码一样使用vector来排序查找一样,还可以使用set来维护块中的数据。只不过将先清空再插入,改为了先删除set中的元素在插入新的元素。

同样引用作者hzwer的话来说,数据n<=100000其实是为了区分暴力和一些常数较大的写法。
接着第二题的解法,其实只要把块内查询的二分稍作修改即可。
不过这题其实想表达:可以在块内维护其它结构使其更具有拓展性,比如放一个 set ,这样如果还有插入、删除元素的操作,会更加的方便。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
typedef long long ll;
const int inf =0x3f3f3f3f;

int n, num, block, belog[maxn], opt, l, r, c;//belog[i]=j,表示a[i]属于第j个分块
int a[maxn], sum[maxn] ;//sum是整块的加法标记
vector<int>v[maxn];//用于排序
vector<int>::iterator it;
inline int read() {//读入挂
    int ret = 0, c, f = 1;
    for(c = getchar(); !(isdigit(c) || c == '-'); c = getchar());
    if(c == '-') f = -1, c = getchar();
    for(; isdigit(c); c = getchar()) ret = ret * 10 + c - '0';
    if(f < 0) ret = -ret;
    return ret;
}
void reset(int x) {//对第x个分块整体排序
    v[x].clear();
    for(int i = (x - 1) * block + 1; i <= min(x * block, n); i++) {
        //因为是整体,所以是i <= min(x * block, n),这样写还是因为最后一个分块可能并灭有block个元素
        v[x].push_back(a[i]);
    }
    sort(v[x].begin(), v[x].end());
}
void updata(int l, int r, int c) {
    int t1 = belog[l];
    int t2 = belog[r];
    for(int i = l; i <= min(t1 * block, r); i++) a[i] += c;//对第一个不完整区间暴力加法
    reset(t1);//对一个修改区间排序
    if(t1 != t2) {
        for(int i = (t2 - 1) * block + 1; i <= r; i++) {//对最后一个区间做暴力加法
            a[i] += c;
        }
        reset(t2);//对最后一个区间排序
    }
    for(int i = t1 + 1; i <= t2 - 1; i++) sum[i] += c;//对整块加上加法标记
}
void query(int l, int r, int c) {
    int t1 = belog[l];
    int t2 = belog[r];
    int ret = -inf;
    int flag = 0;//用于标记有没有比c小的数
    for(int i = l; i <= min(t1 * block, r); i++) {
        if(a[i] + sum[t1] < c) {
            ret = max(a[i] + sum[t1], ret);
            flag = 1;
        }
    }
    if(t1 != t2) {
        for(int i = (t2 - 1) * block + 1; i <= r; i++) {
            if(a[i] + sum[t2] < c) {
                ret = max(a[i] + sum[t2], ret);
                flag = 1;
            }
        }
        for(int i = t1 + 1; i <= t2 - 1; i++) {
            int x = c - sum[i];
            it = lower_bound(v[i].begin(), v[i].end(), x);
            if(it != v[i].begin()) {
            /*lower_bound返回的是第一个>=查找元素的,因此如果返回的v[i].begin(),
            那就说明在当前块中没有比待查找元素小的
            相反如果不是返回v[i].begin(),那就说明存在,因为返回的是>=的值,因此要做
            比较的时候需要it-1.
            */
                flag=1;
                ret=max(ret,*(it-1)+sum[i]);//在更新的时候别忘了要加上sum[i]

            }
        }
    }
    if(!flag) printf("-1\n");
    else printf("%d\n", ret);
}
int main() {
    n = read();
    block =  2*sqrt(n); 
    for(int i = 1; i <= n; i++) {
        a[i] = read();
        belog[i] = (i - 1) / block + 1;
        v[belog[i]].push_back(a[i]);
    }
    for(int i = 1; i <= belog[n]; i++) {
        sort(v[i].begin(), v[i].end());
    }
    for(int j = 1; j <= n; j++) {
        opt = read();
        l = read();
        r = read();
        c = read();
        if(opt) query(l, r, c );
        else updata(l, r, c);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值