头文件 D
题目链接:ybtoj高效进阶 21290
题目大意
给你一个序列,下标从 1 到 n。
然后有两类操作,要么是给出 k,l,r,x 把所有下标 %k 的值在 l~r 之间的位置都加上 x。
要么是区间求和。
思路
发现没有什么比较好的数据结构能实现。
考虑观察它修改的性质。
如果它 \(k\) 很大,那修改的段就很小,可以直接暴力枚举每个段。
如果它 \(k\) 很小,那它修改段很多,但是每段很小,可以暴力用前缀和的方法统计。
所以我们考虑使用平衡规划。
自然分成 \(k\leqslant\sqrt{n}\) 和 \(k>\sqrt{n}\) 两个部分。
第一个部分,我们直接 \(f_{i,j}\) 为 \(k=i\) 的修改,它第 \(j\) 个位置是加了 \(f_{i,j}\)。
然后你在每次搞了就重新计算前缀和 \(g_{i,j}\)。
然后就可以很快的统计了。
第二个部分,直接暴力找每一段线段树赋值,然后插叙就直接查询线段树即可。
代码
#include<cmath>
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
ll n, m, op, x, y, k, z;
ll f[501][501], g[501][501];
ll a[200001], ans, sum[501];
struct XD_tree {//线段树
ll v[200002 << 2], lzy[200002 << 2];
void up(ll now) {
v[now] = v[now << 1] + v[now << 1 | 1];
}
void down(ll now, ll l, ll r) {
if (!lzy[now]) return ;
ll mid = (l + r) >> 1;
v[now << 1] += lzy[now] * (mid - l + 1); v[now << 1 | 1] += lzy[now] * (r - mid);
lzy[now << 1] += lzy[now]; lzy[now << 1 | 1] += lzy[now];
lzy[now] = 0;
}
void build(ll now, ll l, ll r) {
if (l == r) {
v[now] = a[l]; return ;
}
ll mid = (l + r) >> 1;
build(now << 1, l, mid);
build(now << 1 | 1, mid + 1, r);
up(now);
}
void insert(ll now, ll l, ll r, ll L, ll R, ll va) {
if (L <= l && r <= R) {
lzy[now] += va; v[now] += 1ll * va * (r - l + 1);
return ;
}
down(now, l, r);
ll mid = (l + r) >> 1;
if (L <= mid) insert(now << 1, l, mid, L, R, va);
if (mid < R) insert(now << 1 | 1, mid + 1, r, L, R, va);
up(now);
}
ll query(ll now, ll l, ll r, ll L, ll R) {
if (L <= l && r <= R) return v[now];
down(now, l, r);
ll mid = (l + r) >> 1, re = 0;
if (L <= mid) re += query(now << 1, l, mid, L, R);
if (mid < R) re += query(now << 1 | 1, mid + 1, r, L, R);
return re;
}
}T;
int main() {
// freopen("sorrow.in", "r", stdin);
// freopen("sorrow.out", "w", stdout);
scanf("%lld %lld", &n, &m);
for (ll i = 1; i <= n; i++) scanf("%lld", &a[i]);
T.build(1, 1, n);
ll lim = sqrt(n);
while (m--) {
scanf("%lld", &op);
if (op == 1) {
scanf("%lld %lld %lld %lld", &k, &x, &y, &z);
if (k <= lim) {//小的直接暴力前缀和
for (ll i = x; i <= y; i++) {
f[k][i] += z;
}
g[k][0] = f[k][0];
for (int i = 1; i < k; i++)
g[k][i] = g[k][i - 1] + f[k][i];
sum[k] += (y - x + 1) * z;
}
else {
for (ll i = 0; i + x <= n; i += k)
T.insert(1, 1, n, i + x, min(n, i + y), z);
}
}
if (op == 2) {
scanf("%lld %lld", &x, &y);
ans = T.query(1, 1, n, x, y);
for (ll i = 1; i <= lim; i++) {
ll nm1 = x / i, nm2 = y / i;
ans += sum[i] * (nm2 - nm1);
ans += g[i][y % i];//加上后面的一节,减去前面多算的一节
if (x % i != 0) ans -= g[i][x % i - 1];
}
printf("%lld\n", ans);
}
}
return 0;
}