分块 ——区间修改 (luogu P 3372)

联想分块求解的思路,对于块两端边缘暴力处理,对于连续的完整的块直接调用块中内容。

那么很好想到区间修改思路,设sum[i]为第i个块的总和,inc[i]为第i个块所有数全部都要加的值,相当于线段树里的标记。每次把a数组[l,r]区间增加val值,就先将左右两端多出来的,暴力a[i]+=val,并把i所在块的sum也要加上val,然后中间连续完整的块inc[i]+=val。

询问也就很简单了,先把左右两端暴力加上a[i]以及i所在块的inc,然后把中间连续完整块的sum和inc全部加上,就可以得到答案了。

#pragma GCC optimize(3) //这句话用来开O3
#include <cmath>
#include <cstdio>
#include <cstring>

const int N = 100007;
typedef long long ll;

int n, q, op, l, r, len, tot;
ll val, ans, a[N], sum[N / 100], inc[N / 100];
int b[N], lef[N / 100], rig[N / 100];

inline int min(int a, int b) { return a < b ? a : b; } //手打min和max可以加速
inline int max(int a, int b) { return a > b ? a : b; }

inline ll read() //读入加速
{
    ll x = 0, f = 0;
    char c = getchar();
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
    for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + c - '0';
    return f ? -x : x;
}

void init()
{
    memset(inc, 0, sizeof(inc));
    n = read(), q = read();
    len = sqrt(n); //得到每一块的长度
    tot = n / len; //求出块的个数
    if (n % len) //不能正好分割
        tot++; //多一个不完整的块
    for (int i = 1; i <= n; i++)
        *(a + i) = read(), *(b + i) = (i - 1) / len + 1, sum[b[i]] += a[i]; //b[i]表示i所在的块
    for (int i = 1; i <= tot; i++)
        lef[i] = (i - 1) * len + 1, rig[i] = i * len; //块的左右边界
}

void solve()
{
    while (q--)
    {
        op = read(), l = read(), r = read();
        if (op == 1)
        {
            val = read(); //要加的值
            for (int i = l; i <= min(r, rig[b[l]]); i++)
                a[i] += val, sum[b[i]] += val; //左边多出来的部分暴力加
            for (int i = r; i >= max(l, lef[b[r]]); i--)
                a[i] += val, sum[b[i]] += val; //右边多出来的部分暴力加
            for (int i = b[l] + 1; i <= b[r] - 1; i++)
                inc[i] += val; //中间的块inc加上val
        }
        else
        {
            ans = 0;
            for (int i = l; i <= min(r, rig[b[l]]); i++)
                ans += a[i] + inc[b[i]]; //左边的暴力计入答案
            for (int i = r; i >= max(l, lef[b[r]]); i--)
                ans += a[i] + inc[b[i]]; //右边的暴力计入答案
            for (int i = b[l] + 1; i <= b[r] - 1; i++)
                ans += sum[i] + inc[i] * (rig[i] - lef[i] + 1); //将中间完整的块计入答案,注意inc要乘以区间长度,我总是会粗心忘记
            if (b[l] == b[r])
                ans -= a[l] + inc[b[l]] + inc[b[r]] + a[r]; //如果l,r在同一块就会重复,减去重复的两端
            printf("%lld\n", ans);
        }
    }
}

int main()
{
    init();
    solve();
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值