洛谷P2801 教主的魔法 分块

题目:

https://www.luogu.org/problem/show?pid=2801

题意:

题目描述

教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给XMYZ信息组每个英雄看。于是N个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为1、2、……、N。

每个人的身高一开始都是不超过1000的正整数。教主的魔法每次可以把闭区间[L, R](1≤L≤R≤N)内的英雄的身高全部加上一个整数W。(虽然L=R时并不符合区间的书写规范,但我们可以认为是单独增加第L(R)个英雄的身高)

CYZ、光哥和ZJQ等人不信教主的邪,于是他们有时候会问WD闭区间 [L, R] 内有多少英雄身高大于等于C,以验证教主的魔法是否真的有效。

WD巨懒,于是他把这个回答的任务交给了你。

输入输出格式

输入格式:
第1行为两个整数N、Q。Q为问题数与教主的施法数总和。

第2行有N个正整数,第i个数代表第i个英雄的身高。

第3到第Q+2行每行有一个操作:

(1) 若第一个字母为“M”,则紧接着有三个数字L、R、W。表示对闭区间 [L, R] 内所有英雄的身高加上W。

(2) 若第一个字母为“A”,则紧接着有三个数字L、R、C。询问闭区间 [L, R] 内有多少英雄的身高大于等于C。

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

思路:

分块大法好。按每个块大小为sqrt(n)分块,另外开一个数组,把原始数组复制过去,块内元素保持有序。对每个块维护一个增量,代表块内的所有元素应该增加的数量,更新时,对完整的块,更新增量即可,不完整的块,直接暴力更新。查询时,对不完整的块,暴力查询,对于完整的块,二分查询块内大于等于c的数量。注意二分的细节,我写的时候是找到第一个大于等于c的位置,然后块的右端减去这个位置再加1,但是块内元素可能全小于c,这时这个位置应该设成超尾的,谨记

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

const int N = 1e6 + 10;

int n, m;
int block, sz;
int pos[N], L[N], R[N], add[N];
int a[N], b[N];
void reset(int x)
{
    for(int i = L[x]; i <= R[x]; i++) b[i] = a[i];
    sort(b + L[x], b + R[x] + 1);
}
void init()
{
    block = (int)sqrt(n);
    sz = n / block;
    if(n % block) sz++;
    for(int i = 1; i <= n; i++) pos[i] = (i-1) / block + 1;
    for(int i = 1; i <= sz; i++)
    {
        L[i] = (i-1) * block + 1;
        R[i] = i * block;
    }
    R[sz] = n;
    for(int i = 1; i <= sz; i++) reset(i);
}
void update(int l, int r, int c)
{
    int lb = pos[l], rb = pos[r];
    if(lb == rb)
    {
        for(int i = l; i <= r; i++) a[i] += c;
        reset(lb);
    }
    else
    {
        for(int i = l; i <= R[lb]; i++) a[i] += c;
        for(int i = L[rb]; i <= r; i++) a[i] += c;
        for(int i = lb + 1; i < rb; i++) add[i] += c;
        reset(lb);
        reset(rb);
    }
}
int bin_serach(int x, int c)
{
    int l = L[x], r = R[x];
    int ans = R[x] + 1;//初始设为超尾
    while(l <= r)
    {
        int mid = (l + r) / 2;
        if(b[mid] >= c) r = mid - 1, ans = mid;
        else l = mid + 1;
    }
    return R[x] - ans + 1;
}
int query(int l, int r, int c)
{
    int lb = pos[l], rb = pos[r];
    int ans = 0;
    if(lb == rb)
    {
        for(int i = l; i <= r; i++)
            if(a[i] + add[lb] >= c) ans++;
    }
    else
    {
        for(int i = l; i <= R[lb]; i++)
            if(a[i] + add[lb] >= c) ans++;
        for(int i = L[rb]; i <= r; i++)
            if(a[i] + add[rb] >= c) ans++;
        for(int i = lb + 1; i < rb; i++)
            ans += bin_serach(i, c - add[i]);
    }
    return ans;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    init();
    char ch;
    int a, b, c;
    for(int i = 1; i <= m; i++)
    {
        scanf(" %c%d%d%d", &ch, &a, &b, &c);
        if(ch == 'M') update(a, b, c);
        else printf("%d\n", query(a, b, c));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值