【洛谷】P2801 教主的魔法

题目地址:

https://www.luogu.com.cn/problem/P2801

题目描述:
教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给XMYZ信息组每个英雄看。于是 N N N个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为 1 , 2 , … , N 1, 2, \ldots, N 1,2,,N。每个人的身高一开始都是不超过 10001000 10001000 10001000的正整数。教主的魔法每次可以把闭区间 [ L , R ] [L, R] [L,R] 1 ≤ L ≤ R ≤ N 1≤L≤R≤N 1LRN)内的英雄的身高全部加上一个整数 W W W。(虽然 L = R L=R L=R时并不符合区间的书写规范,但我们可以认为是单独增加第 L ( R ) L(R) L(R)个英雄的身高)CYZ、光哥和ZJQ等人不信教主的邪,于是他们有时候会问WD闭区间 [ L , R ] [L, R] [L,R]内有多少英雄身高大于等于 C C C,以验证教主的魔法是否真的有效。WD巨懒,于是他把这个回答的任务交给了你。

输入格式:
1 1 1行为两个整数 N , Q N, Q N,Q Q Q Q为问题数与教主的施法数总和。
2 2 2行有 N N N个正整数,第 i i i个数代表第 i i i个英雄的身高。
3 3 3到第 Q + 2 Q+2 Q+2行每行有一个操作:若第一个字母为 M M M,则紧接着有三个数字 L , R , W L, R, W L,R,W。表示对闭区间 [ L , R ] [L, R] [L,R]内所有英雄的身高加上 W W W。若第一个字母为 A A A,则紧接着有三个数字 L , R , C L, R, C L,R,C。询问闭区间 [ L , R ] [L, R] [L,R]内有多少英雄的身高大于等于 C C C

输出格式:
对每个 A A A询问输出一行,仅含一个整数,表示闭区间 [ L , R ] [L, R] [L,R]内身高大于等于 C C C的英雄数。

数据范围:
对于 30 % 30\% 30%的数据, N ≤ 1000 N≤1000 N1000 Q ≤ 1000 Q≤1000 Q1000。对于 100 % 100\% 100%的数据, N ≤ 1 0 6 N≤10^6 N106 Q ≤ 3000 Q≤3000 Q3000 1 ≤ W ≤ 1000 1≤W≤1000 1W1000 1 ≤ C ≤ 1 0 9 1≤C≤10^9 1C109

其实是要实现一个数据结构可以做两种操作,一个是区间增加某个数 x x x,另一个是查询区间里大于等于某个数的个数有多少个。可以用分块的思想来做。将 [ l , r ] [l,r] [l,r]里的数增加 v v v,这个操作可以用分块 + 懒标记来做,但是求 [ l , r ] [l,r] [l,r]里大于等于 v v v的数的个数,这个操作光靠分块是不行的,可以将每个块都存下来并保持有序,这样整块内就可以二分来做,在散块里的还需要暴力求解。综上,两个操作我们这样做:
1、初始化的时候,开个vector向量 v v v v [ i ] v[i] v[i]存第 i i i个分块里的所有数,并排好序( v [ i ] v[i] v[i]本身是个vector);
2、区间 [ l , r ] [l,r] [l,r]增加 v v v的操作,如果区间属于一个整块,则直接暴力做;对于在散块里的数,也直接暴力做。这两种情况下做完了要将 v [ i ] v[i] v[i]更新并排序;而对于整块里的数,只做懒标记;
3、区间 [ l , r ] [l,r] [l,r]里查询大于等于 v v v的数的个数,如果区间属于一个整块,或者对于在散块里的数,这两种情况都是暴力计数(注意要加懒标记);对于在整块里的数,由于整块里的数在 v [ i ] v[i] v[i]里都排好序了,所以可以直接二分来计数(也要加懒标记)。

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;

const int N = 1e6 + 10, M = 1050;
int n, q, len;
int w[N], lazy[M];
vector<int> BQ[M];

// 得到i是属于第几个分块
int get(int i) {
    return i / len;
}

void init() {
    for (int i = 0; i < n; i++) BQ[get(i)].push_back(w[i]);
    for (int i = 0; i < n; i += len) sort(BQ[get(i)].begin(), BQ[get(i)].end());
}

// 更新第u个分块
void update(int u) {
    vector<int> &v = BQ[u];
    // 清空后把数重新加进去并排序
    v.clear();
    for (int i = u * len; i < min(n, (u + 1) * len); i++)
        v.push_back(w[i]);

    sort(v.begin(), v.end());
}

// 将区间[l, r]中每个数增加v
void add(int l, int r, int v) {
    if (get(l) == get(r)) {
    	// 属于同一个整块,暴力做完后更新这个整块
        for (int i = l; i <= r; i++) w[i] += v;
        update(get(l));
    } else {
    	// 先将散块暴力更新然后更新块的信息
        int i = l, j = r;
        while (get(i) == get(l)) w[i++] += v;
        update(get(l));
        while (get(j) == get(r)) w[j--] += v;
        update(get(r));
        // 对于整块只做懒标记即可
        for (int k = get(i); k <= get(j); k++) lazy[k] += v;
    }
}

// 查询[l, r]里大于等于v的数的个数
int query(int l, int r, int v) {
    int res = 0;
    if (get(l) == get(r)) {
    	// 如果在一个整块里,则直接暴力计数
        for (int i = l; i <= r; i++)
            if (w[i] + lazy[get(l)] >= v) res++;
    } else {
    	// 否则先暴力计数在散块里的数
        int i = l, j = r;
        while (get(i) == get(l)) {
            if (w[i] + lazy[get(i)] >= v) res++;
            i++;
        }
        while (get(j) == get(r)) {
            if (w[j] + lazy[get(j)] >= v) res++;
            j--;
        }
        // 对于整块,由于整块已经都排好序了,所以可以直接二分求解
        for (int k = get(i); k <= get(j); k++) {
            vector<int> &vec = BQ[k];
            int l = 0, r = vec.size() - 1;
            while (l < r) {
                int mid = l + (r - l >> 1);
                if (vec[mid] + lazy[k] >= v) r = mid;
                else l = mid + 1;
            }

            if (vec[l] + lazy[k] >= v) res += vec.size() - l;
        }
    }

    return res;
}

int main() {
    scanf("%d%d", &n, &q);
    len = sqrt(n);
    for (int i = 0; i < n; i++) scanf("%d", &w[i]);

    init();

    while (q--) {
        char op[2];
        int l, r, v;
        scanf("%s%d%d%d", op, &l, &r, &v);
        if (op[0] == 'A') printf("%d\n", query(l - 1, r - 1, v));
        else add(l - 1, r - 1, v);
    }

    return 0;
}

初始化时间复杂度 O ( N log ⁡ N ) = O ( N log ⁡ N ) O(N {\log \sqrt N})=O(N\log N) O(NlogN )=O(NlogN),每次操作时间复杂度 O ( N log ⁡ N ) = O ( N log ⁡ N ) O(\sqrt N\log \sqrt N)=O(\sqrt N\log N) O(N logN )=O(N logN),空间 O ( N ) O(N) O(N)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值