数列分块入门2--LOJ(区间小于x的个数)

本文介绍了一种基于分块的算法,用于处理包含区间加法和区间内小于特定值元素计数的操作。通过预处理和排序,算法能在合理的时间复杂度内解决这类问题。

题目链接https://loj.ac/problem/6278

内存限制:256 MiB时间限制:500 ms

题目描述

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

输入格式

第一行输入一个数字 。

第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。

接下来输入n  行询问,每行输入四个数字 opt,l,r,c以空格隔开。

若pot =0  ,表示将位于[l,r]  的之间的数字都加 。

若opt =1 ,表示询问 [l,r]中,小于c^2的数字的个数。

输出格式

对于每次询问,输出一行一个数字表示答案。

样例

样例输入

4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2

样例输出

3
0
2

数据范围与提示

对于 100% 的数据1<=n<=50000,-2^31<=others,ans<=2^31 -1.


这题和上一题差不多,只不过有个操作比较难以完成,就是查找区间小于x数的个数。

按照分块一贯的尿性,我们先将数列分为sqrt(n)块,然后计算一下它的块的端点,和每个点所属的块,我们这里用另外一个数组d复制一下a

int n;
cin >> n;
int t = sqrt(n);
for (int i = 1; i <= n; i++) cin >> a[i], d[i] = a[i];
for (int i = 1; i <= t; i++) {
     L[i] = (i - 1) * t + 1;
     R[i] = i * t;
}
if (R[t] < n)
    t++, L[t] = R[t - 1] + 1, R[t] = n;
for (int i = 1; i <= t; i++)
    for (int j = L[i]; j <= R[i]; j++) id[j] = i;

接下来我们要干什么呢?当然是将每个块有序化:

for (int i = 1; i <= t; i++) 
    sort(d + L[i], d + R[i] + 1);

这样操作之后每个块内的元素都是有序的,但又不影响原来的数组。

接下来就是常规操作了,对于update而言,我们在分块1的基础上多了一个操作:

int ll = id[l], rr = id[r];
for (int i = l; i <= R[ll]; i++) a[i] += w;
for (int i = L[ll]; i <= R[ll]; i++) d[i] = a[i];
sort(d + L[ll], d + R[ll] + 1);

也就是说l所属的块的元素的顺序被打乱了,我们需要重新排序。对于不属于l和r的块我们就不需要做任何操作,因为块中的每个元素加上同一个数的大小关系是不变的。

那么询问的时候也是老样子,对于l和r所属的块暴力查询,对l到r之间的块,由于它们是有序的,我们只需要找到第一个大于等于他的就好了,所以我们二分一下就出来了:

for (int i = ll + 1; i <= rr - 1; i++) {
     int x = w - add[i];
     ans += lower_bound(d + L[i], d + R[i] + 1, x) - (d + L[i]);
}

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac = 5e4 + 10;

int a[mac], add[300], id[mac], L[mac], R[mac];
int d[mac];

void update(int l, int r, int w) {
    int ll = id[l], rr = id[r];
    if (ll == rr) {
        for (int i = l; i <= r; i++) a[i] += w;
        for (int i = L[ll]; i <= R[ll]; i++) d[i] = a[i];
        sort(d + L[ll], d + R[ll] + 1);
    } else {
        for (int i = l; i <= R[ll]; i++) a[i] += w;
        for (int i = L[ll]; i <= R[ll]; i++) d[i] = a[i];
        sort(d + L[ll], d + R[ll] + 1);

        for (int i = ll + 1; i <= rr - 1; i++) add[i] += w;

        for (int i = L[rr]; i <= r; i++) a[i] += w;
        for (int i = L[rr]; i <= R[rr]; i++) d[i] = a[i];
        sort(d + L[rr], d + R[rr] + 1);
    }
}

int query(int l, int r, int w) {
    int ll = id[l], rr = id[r];
    int ans = 0;
    if (ll == rr) {
        for (int i = l; i <= r; i++)
            if (a[i] + add[ll] < w)
                ans++;
    } else {
        for (int i = l; i <= R[ll]; i++)
            if (a[i] + add[ll] < w)
                ans++;
        for (int i = L[rr]; i <= r; i++)
            if (a[i] + add[rr] < w)
                ans++;

        for (int i = ll + 1; i <= rr - 1; i++) {
            int x = w - add[i];
            ans += lower_bound(d + L[i], d + R[i] + 1, x) - (d + L[i]);
        }
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    int t = sqrt(n);
    for (int i = 1; i <= n; i++) cin >> a[i], d[i] = a[i];
    for (int i = 1; i <= t; i++) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n)
        t++, L[t] = R[t - 1] + 1, R[t] = n;
    for (int i = 1; i <= t; i++)
        for (int j = L[i]; j <= R[i]; j++) id[j] = i;
    for (int i = 1; i <= t; i++) sort(d + L[i], d + R[i] + 1);
    for (int i = 1; i <= n; i++) {
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if (opt) {
            int ans = query(l, r, c * c);
            cout << ans << endl;
        } 
        else update(l, r, c);
    }
    return 0;
}

 

4 0 0 0 0 3 0 1 2 1 1 1 2 2 0 1 2 1 1 1 2 3 我可以正常通过您的多个 hack,另外输入格式中写的是,数组长度和询问个数一样多。 附上题面和待调试代码: # T665247 【模板题】数列分块入门 2 ## 题目描述 给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及: - 区间加法。 - 询问区间小于某个值 $x$ 的元素个数。 ## 输入格式 第一行输入一个数字 $n$。 第二行输入 $n$ 个数字,第 $i$ 个数字为 $a_i$,以空格隔开。 接下来输入 $n$ 行询问,每行输入四个数字 $\mathrm{opt}$、$l$、$r$、$c$,以空格隔开。 - 若 $\mathrm{opt} = 0$,表示将位于 $[l, r]$ 的之间的数字都加 $c$。 - 若 $\mathrm{opt} = 1$,表示询问 $[l, r]$ 中,小于 $c^2$ 的数字的个数。 ## 输出格式 对于每次询问,输出一行一个数字表示答案。 ## 输入输出样例 #1 ### 输入 #1 ``` 4 1 2 2 3 0 1 3 1 1 1 3 2 1 1 4 1 1 2 3 2 ``` ### 输出 #1 ``` 3 0 2 ``` ## 说明/提示 对于所有数据,$1 \leq n \leq 2\times 10^5$,$-2^{31} \leq a_i,c,\mathrm{ans} \leq 2^{31}-1$,$\mathrm{opt} \in \{0,1\}$,$1 \leq l \leq r \leq n$。 测试数据**不保证**每次操作后的 $a_i$ 满足 $-2^{31} \leq a_i \leq 2^{31}-1$。 #include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; const int N = 2e5 + 10, M = 1e3 + 10; int n, len; LL w[N], add[M]; int id[N]; int L[M], R[M]; bool sorted[M]; void modify(int l, int r, LL c) { if (id[l] == id[r]) { for (int i = l; i <= r; ++ i ) w[i] += c; sorted[id[l]] = false; } else { int i = l, j = r; while (id[i] == id[l]) w[i] += c, ++ i ; while (id[j] == id[r]) w[j] += c, -- j ; sorted[id[l]] = sorted[id[r]] = false; for (int k = id[i]; k <= id[j]; ++ k ) add[k] += c; } } int search(int l, int r, LL c) { int res = 0; for (int i = l; i <= r; ++ i ) res += w[i] + add[id[l]] < c; return res; } int binary(int x, LL c) { if (!sorted[x]) { sort(w + L[x], w + R[x] + 1); sorted[x] = true; } int l = L[x], r = R[x]; while (l < r) { int mid = l + r + 1 >> 1; if (w[mid] + add[x] < c) l = mid; else r = mid - 1; } if (w[l] + add[x] >= c) return 0; return l - L[x] + 1; } int query(int l, int r, LL c) { int i = id[l], j = id[r], res = 0; if (i == j) res = search(l, r, c); else { res = search(l, R[i], c); res += search(L[j], r, c); for (int k = i + 1; k < j; ++ k ) res += binary(k, c); } return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; len = sqrt(n); memset(L, 0x3f, sizeof L); for (int i = 1; i <= n; ++ i ) { cin >> w[i]; int j = id[i] = i / len; L[j] = min(L[j], i), R[j] = max(R[j], i); } for (int i = 0; i < n; ++ i ) { int op, l, r; LL c; cin >> op >> l >> r >> c; if (!op) modify(l, r, c); else cout << query(l, r, c * c) << '\n'; } return 0; }
最新发布
10-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值