联想分块求解的思路,对于块两端边缘暴力处理,对于连续的完整的块直接调用块中内容。
那么很好想到区间修改思路,设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;
}