【ybtoj高效进阶 21290】头文件 D(平衡规划)(线段树)

头文件 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;
}
上一篇:北航數值分析第一次大作業


下一篇:leetcode1825,802,583,501