这题题解的做法千奇百怪,有写了两棵线段树的,有线段树套差分的,还有线段树套二阶差分的。我承认是我看不懂所以我决定写一篇只用一棵线段树的题解。
分析
众所周知,普通线段树的懒标记存的是一个待更新的量。那对于这个题来说,直接存和(也就是 add 操作在这个线段上的影响)肯定是不切实际的,因为知道了这个线段上待更新的和之后并不能很方便的计算出两个子线段上待更新的和。那既然不能直接存,那就间接存。我们可以把懒标记稍微改一下,使得这个懒标记可以很方便的传递,并且通过懒标记可以推出唯一的等差数列。这时,你可能已经想到了:可以在懒标记上存下等差数列的首项和公差,这样传递也方便,计算也方便。那么现在每个节点上的懒标记就是首项和公差,它代表了一个等差数列。所以每个节点上的懒标记都是一个等差数列。
对于下传来说,我们可以直接把两个懒标记上的首项相加,公差相加,就完成了下传。这样的正确性显而易见。设有两个等长的等差数列 \(a\) 和 \(b\),\(a\) 的首项是 \(s_1\),公差是 \(d_1\),\(b\) 的首项是 \(s_2\),公差是 \(d_2\),长度都为 \(l\),则他们的和就是 \(s_1*l+s_2*l+l*[(l-1)/2]*d_1+l*[(l-1)/2]*d_2\),也就是 \((s_1+s_2)*l+l*[(l-1)/2]*(d_1+d_2)\),也就相当于一个首项为 \((s_1+s_2)\),公差为 \((d_1+d_2)\) 的等差数列的和。
对于计算来说,我觉得我不用多说。等差数列求和公式大家应该都会。
需要注意的是,在传递懒标记给右儿子时,首项需要改变,打懒标记的时候首项也要根据该线段左端点与 add 区间左端点的距离改变。
代码
#include <iostream>
#define int long long // 三年OI一场空,不开long long见祖宗!!!
#define st first
#define dif second
using namespace std;
const int N = 131072;
int arr[N * 4], sm[N * 4];
pair<int, int> tag[N * 4];
void Build(int o, int l, int r) {
if (l == r) {
sm[o] = arr[l];
return;
}
int mid = l + r >> 1;
Build(o << 1, l, mid); // 建左儿子
Build(o << 1 | 1ll, mid + 1, r); // 建右儿子
sm[o] = sm[o << 1] + sm[o << 1 | 1ll]; // 上传
}
inline int getsum(int l, int r, int L, int R, int k, int d) { // 已知公差和 add 区间与当前区间的求和
int bg = k + d * (l - L); // 求首项
int ed = k + d * (r - L); // 求末项
return (bg + ed) * (r - l + 1) / 2; // 求和
}
inline int getsum2(int bg, int dif, int l) { // 已知首项 公差 长度的求和
int ed = bg + dif * (l - 1); // 求末项
return (bg + ed) * l / 2; // 求和
}
void pushdown(int o, int l, int r) {
if (tag[o].st == 0 && tag[o].dif == 0)
return;
int nst = tag[o].st, ndif = tag[o].dif; // 当前区间懒标记的首项及公差
int mid = l + r >> 1;
tag[o].st = tag[o].dif = 0;
sm[o << 1] += getsum2(nst, ndif, mid - l + 1); // 给左儿子加上
sm[o << 1 | 1ll] += getsum2(nst + (mid - l + 1) * ndif, ndif, r - mid); // 给右儿子加上
tag[o << 1].st += nst, tag[o << 1].dif += ndif; // 更新左儿子的懒标记
tag[o << 1 | 1ll].st += nst + (mid - l + 1) * ndif, tag[o << 1 | 1ll].dif += ndif; // 更新右儿子的等差数列
}
void add(int o, int l, int r, int L, int R, int k, int d) {
if (L <= l && r <= R) {
int tmp = k + d * (l - L); // 这里是首项
tag[o].st += tmp; // 打懒标记
tag[o].dif += d;
sm[o] += getsum(l, r, L, R, k, d); // 更新当前区间和
return;
}
pushdown(o, l, r); // 进行一个下传
int mid = l + r >> 1;
if (L <= mid)
add(o << 1, l, mid, L, R, k, d);
if (R > mid)
add(o << 1 | 1ll, mid + 1, r, L, R, k, d);
}
int query(int o, int l, int r, int x) { // 线段树标准query
if (l == r)
return sm[o];
pushdown(o, l, r);
int mid = l + r >> 1;
if (x <= mid)
return query(o << 1, l, mid, x);
else
return query(o << 1 | 1ll, mid + 1, r, x);
}
signed main() { // 以下都是板子
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) scanf("%lld", arr + i);
Build(1, 1, N);
for (int i = 0, c, l, r, k, d; i < m; i++) {
scanf("%lld", &c);
if (c == 1) {
scanf("%lld%lld%lld%lld", &l, &r, &k, &d);
add(1, 1, N, l, r, k, d);
} else {
scanf("%lld", &l);
printf("%lld\n", query(1, 1, N, l));
}
}
return 0;
}
完结撒花~~~
标签:洛谷,数列,标记,int,线段,公差,首项,等差数列,P1438 From: https://www.cnblogs.com/forgotmyhandle/p/18000234