线段树算法详解与实现
线段树常见场景
节点最大个数为区间长度4倍
单点修改模版P3374 【模板】树状数组 1 - 洛谷
#include<iostream>
using namespace std;
#define lc p<<1
#define rc p<<1|1
const int N = 5e5 + 10;
int n, m;
int a[N];
struct node
{int l, r, sum;
}tr[4*N];
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}
void build(int p, int l, int r)
{tr[p] = { l,r,0 };if (l == r){tr[p].sum = a[l]; return;}int mid = (l + r) / 2;build(lc, l, mid);build(rc, mid + 1, r);pushup(p);
}
int query(int p,int x,int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].sum;int ret = 0; int mid = (l + r) / 2;if (x <= mid)ret += query(lc, x, y);if (y >= mid + 1)ret += query(rc, x, y);return ret;
}
void modify(int p, int x, int k)
{int l = tr[p].l, r = tr[p].r;if (l == r){tr[p].sum += k; return;}int mid = (l + r) / 2;if (x <= mid)modify(lc, x, k);else modify(rc, x, k);pushup(p);
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);for (int i = 1; i <= m; i++){int a; cin >> a;if (a == 1){int x, k; cin >> x >> k;modify(1, x, k);}else{int x, y; cin >> x >> y;cout<<query(1, x, y)<<endl;}}return 0;
}
区间修改及查询操作案例:P3372 【模板】线段树 1 - 洛谷
经常不注意会踩的坑:(以区间+操作为例)
1.sum,add操作时均为+=
2.modify在修改左右子树前也要pushdown下放懒标记(modify中的最后操作会pushup)
3.query操作中记得pushdown
4.pushdown操作中记得判断add
const int N = 1e5 + 10;
#define lc p<<1
#define rc p<<1|1
typedef long long LL;
LL a[N]; int n, m;
struct node
{int l, r;LL sum, add;
}tr[4*N];
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}void build(int p, int l, int r)
{tr[p] = { l,r,0,0 };if (l == r){tr[p].sum = a[l];return;}int mid = (l + r)/2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);}
void lazy(int p, LL add)
{//已在pushdown检查是否有懒标记了,此处记得均为+=int l = tr[p].l, r = tr[p].r;tr[p].sum += (r - l + 1) *add;tr[p].add += add;
}
void pushdown(int p)
{if(tr[p].add)//先看是否有懒标记{lazy(lc, tr[p].add);lazy(rc, tr[p].add);tr[p].add = 0;}
}
void modify(int p, int x, int y, int k)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r){/*tr[p].sum += (r - l + 1) * k;tr[p].add += k;*/lazy(p, k);return;}pushdown(p);//因为modify最后的操作是pushup,多次modify时为防止原值pushup导致算错,要pushdown更新int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y, k);if (y> mid)modify(rc, x, y, k);pushup(p);}
LL query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].sum;pushdown(p); //懒标记下放int mid = (l + r) / 2; LL ret = 0;if (x <= mid)ret+=query(lc, x, y);if (y > mid)ret += query(rc, x, y);return ret;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);for (int i = 1; i <= m; i++){int a; cin >> a;if (a == 1){int x, y, k; cin >> x >> y >> k;modify(1, x, y, k);}else{int x, y; cin >> x >> y;cout << query(1, x, y) << endl;}}return 0;
}
重难点:线段树多个区间修改操作
1.P3373 【模板】线段树 2 - 洛谷
既有+又有*,无法判断谁先操作,那就假设规定(推导数学公式来保证正确性)
在懒标记下放时我们规定:每一步都是
先加后乘,推导数学式如下图,我们发现更新add时(a'=a+A/m)可能会导致精度丢失
而先乘后加我们发现不存在此问题,故而使用它
s1表示的是该区间原来之和,最后将其拆出是为了延续公式的一般性
s2表示的是该区间现在之和
s3表示的是懒标记传递至此处时该区间更新后之和
#include<iostream>
using namespace std;
typedef long long LL;
//此题数据范围极大,LL也会越界,必须在不断更新区间时就对其进行摸操作,
//只在query是取模仍会出现负数
#define lc p<<1
#define rc p<<1|1
const int N = 1e5 + 10;
struct node
{int l, r;LL sum, add, mul;
}tr[N<<2];
int n, q, mod;
int a[N];
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],0,1 };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);
}
void lazy(int p, LL add, LL mul)
{tr[p].sum = (tr[p].sum * mul + (tr[p].r - tr[p].l + 1) * add)%mod;tr[p].add = (tr[p].add * mul + add)%mod;tr[p].mul = (tr[p].mul * mul)%mod;
}
// void lazy(int p, LL add, LL mul)
// {
// int len = tr[p].r - tr[p].l + 1;
// tr[p].sum = tr[p].sum * mul + add * len;
// tr[p].mul *= mul;
// tr[p].add = tr[p].add * mul + add;
// }
void pushdown(int p)
{if (tr[p].add != 0 || tr[p].mul != 1){lazy(lc, tr[p].add, tr[p].mul);lazy(rc, tr[p].add, tr[p].mul);tr[p].add = 0;tr[p].mul = 1;}
}
void modify(int p, int x, int y, LL add, LL mul)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r){lazy(p, add, mul);return;}pushdown(p);int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y, add, mul);if (y > mid)modify(rc, x, y, add, mul);pushup(p);
}
LL query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].sum;pushdown(p);int mid = (l + r) / 2; LL ret = 0;if (x <= mid)ret += query(lc, x, y);if (y > mid)ret += query(rc, x, y);return ret%mod;
}
int main()
{cin >> n >> q >> mod;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);while (q--){int op, x, y, k;cin >> op;if (op == 1){cin >> x >> y >> k;modify(1, x, y, 0, k);}else if (op == 2){cin >> x >> y >> k;modify(1, x, y, k, 1);}else{cin >> x >> y;cout << query(1, x, y) << endl;;}}}
2.P1253 扶苏的问题 - 洛谷
#include<iostream>
using namespace std;
typedef long long LL;
#define lc p<<1
#define rc p<<1|1
const int N = 1e6 + 10;
int a[N];
int n, m;
struct node
{int l, r;LL max, add, update;bool st;
}tr[N<<2];
void pushup(int p)
{tr[p].max = max(tr[lc].max, tr[rc].max);
}
void build(int p,int l,int r)
{tr[p] = { l,r,a[l],0,0,false };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);
}
void lazy(int p, LL add, LL update, bool st)
{if (st){tr[p].max = update;tr[p].add = 0;tr[p].update = update;tr[p].st = true;}tr[p].max += add;tr[p].add += add;}
void pushdown(int p)
{lazy(lc, tr[p].add, tr[p].update, tr[p].st);lazy(rc, tr[p].add, tr[p].update, tr[p].st);tr[p].add = tr[p].update = tr[p].st = 0;
}
void modify(int p, int x, int y, LL add, LL update,bool st)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r){lazy(p, add, update,st);return;}pushdown(p);int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y, add, update,st);if (y > mid)modify(rc, x, y, add, update,st);pushup(p);
}
LL query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].max;pushdown(p);int mid = (l + r) / 2;LL ret = -1e18;if (x <= mid)ret = max(ret, query(lc, x, y));if (y > mid)ret = max(ret, query(rc, x, y));return ret;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)scanf("%d", &a[i]);build(1, 1, n);for (int i = 1; i <= m; i++){int op; scanf("%d",&op);if (op == 1){int x, y, k; scanf("%d%d%d", &x, &y, &k);modify(1, x, y, 0, k, true);}else if (op == 2){int x, y, k; scanf("%d%d%d", &x, &y, &k);modify(1, x, y, k, 0, false);}else{int x, y; scanf("%d%d", &x, &y);printf("%ld\n",query(1,x,y));}}return 0;
}
练习部分
P3368 【模板】树状数组 2 - 洛谷
typedef long long LL;
#define lc p<<1
#define rc p<<1|1
const int N = 5e5 + 10;
int a[N];
int n, m;
struct node
{int l, r;LL sum, add;
}tr[N << 2];
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],0 };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);
}
void lazy(int p, int k)
{tr[p].sum += (tr[p].r - tr[p].l + 1) * k;tr[p].add += k;
}
void pushdown(int p)
{if (tr[p].add){lazy(lc, tr[p].add);lazy(rc, tr[p].add);tr[p].add = 0;}
}
void modify(int p, int x, int y, int k)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r){lazy(p, k);return;}pushdown(p);int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y, k);if (y > mid)modify(rc, x, y, k);pushup(p);
}
int query(int p, int x)
{int l = tr[p].l, r = tr[p].r;if (l == r)return tr[p].sum;pushdown(p);int mid = (l + r) / 2; int ret = 0;if (x <= mid)ret += query(lc, x);else ret += query(rc, x);return ret;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)scanf("%d", &a[i]);build(1, 1, n);for (int i = 1; i <= m; i++){int a; cin >> a;if (a == 1){int x, y, k; cin >> x >> y >> k;modify(1, x, y, k);}else{int x; cin >> x;printf("%d\n", query(1, x));}}
}
P3870 [TJOI2009] 开关 - 洛谷
#include<iostream>
using namespace std;
#define lc p<<1
#define rc p<<1|1
const int N = 2e5 + 10;
struct node
{int l, r, ret, cnt;
}tr[N<<2];
int n, m;
void pushup(int p)
{tr[p].ret = tr[lc].ret + tr[rc].ret;
}
void build(int p, int l, int r)
{tr[p] = { l,r,0,0 };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid);build(rc, mid + 1, r);
}
void lazy(int p)
{tr[p].ret = (tr[p].r - tr[p].l + 1) - tr[p].ret;tr[p].cnt += 1;
}
void pushdown(int p)
{if (tr[p].cnt % 2){lazy(lc);lazy(rc);}tr[p].cnt = 0;
}
void modify(int p,int x,int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r){lazy(p);return;}pushdown(p);int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y);if (y > mid)modify(rc, x, y);pushup(p);
}
int query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].ret;pushdown(p);int mid = (l + r) / 2; int ret = 0;if (x <= mid)ret += query(lc, x, y);if (y > mid)ret += query(rc, x, y);return ret;
}
int main()
{cin >> n >> m;build(1, 1, n);for (int i = 1; i <= m; i++){int a,x,y; cin >> a>>x>>y;if (!a){modify(1, x, y);}else{cout << query(1, x, y) << endl;}}return 0;
}
P1438 无聊的数列 - 洛谷
本题是区间等差修改+单点查询操作,故而可用线段树+差分数组。可减少代码细节错误率
#include<iostream>
using namespace std;
typedef long long LL;
#define lc p<<1
#define rc p<<1|1
const int N = 1e5 + 10;
int a[N], n, m;
struct node
{LL l, r, sum, A, D;
}tr[N<<2];
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],0,0 };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);
}
void lazy(int p, LL a, LL d)
{int len = tr[p].r - tr[p].l + 1;tr[p].sum += len * a + len * (len - 1) * d / 2;tr[p].A += a;tr[p].D += d;
}
void pushdown(int p)
{if (tr[p].A||tr[p].D){int l = tr[p].l, r = tr[p].r;int mid = (r+l) / 2;lazy(lc, tr[p].A, tr[p].D);lazy(rc, tr[p].A+(mid+1-l)*tr[p].D, tr[p].D);//虽然能pushdown说明此范围全是可修改区域,但不能用mid*d,而是(mid+1-l)*d,要的是区间长度tr[p].A = 0;tr[p].D = 0;}
}
void modify(int p, int x, int y, LL a, LL k)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r){lazy(p, a+(l-x)*k, k);return;}pushdown(p);int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y, a, k);if (y > mid)modify(rc, x, y, a, k);pushup(p);
}
LL query(int p, int x)
{int l = tr[p].l, r = tr[p].r;if (l == r)return tr[p].sum;pushdown(p);int mid = (l + r) / 2; LL ret = 0;if (x <= mid)ret+=query(lc, x);else ret+=query(rc, x);return ret;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);for (int i = 1; i <= m; i++){int a; cin >> a;if (a == 1){int x, y, a, k; cin >> x >> y >> a >> k;modify(1, x, y, a, k);}else{int x; cin >> x;cout << query(1, x) << endl;}}
}
重难点:线段树+分治
1.P4513 小白逛公园 - 洛谷
#include<iostream>
using namespace std;
#define lc p<<1
#define rc p<<1|1const int N = 5e5 + 10;
int n, m, a[N];
struct node
{int l, r, max, lmax, rmax, sum;
}tr[N<<2];
void pushup(node& p,node& l,node&r)
{p.max = max(max(r.max, l.max), l.rmax+r.lmax);p.lmax = max(l.lmax, l.sum + r.lmax);p.rmax = max(r.rmax, r.sum + l.rmax);p.sum = l.sum + r.sum;
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],a[l],a[l],a[l]};if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(tr[p],tr[lc],tr[rc]);
}
void modify(int p, int x, int k)
{int l = tr[p].l, r = tr[p].r;if (l == r){tr[p].max = tr[p].lmax = tr[p].rmax = tr[p].sum = k;return;}int mid = (l + r) / 2;if (x <= mid)modify(lc, x, k);else modify(rc, x, k);pushup(tr[p], tr[lc], tr[rc]);}
node query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p];int mid = (l + r) / 2;if (y <= mid)return query(lc, x, y);if (x > mid)return query(rc, x, y);node ret, L = query(lc, x, y), R = query(rc, x, y);pushup(ret, L, R);return ret;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);for (int i = 1; i <= m; i++){int op; cin >> op;if (op == 1){int x, y; cin >> x >> y;if (x > y)swap(x, y);cout<<query(1, x, y).max<<endl;}else{int x, k; cin >> x >> k;modify(1, x, k);}}
}
线段树+剪枝
P4145 上帝造题的七分钟 2 / 花神游历各国 - 洛谷
区间修改+区间查询 但我们发现区间无法直接修改,即无法正常使用懒标记(正常使用懒标记是指:当前节点可以直接修改,并不依赖子节点)。
此题意味着修改时我们需要每次都遍历子节点,时间复杂度为m*n*logn,还不如直接使用数组(复杂度为n*m),那我们看看能否使用剪枝操作来降低时间复杂度。
思路:
我们又发现10^12最多开根6次变成了1,到了1时就没必要开根了(1开根仍为1)
那我们在node节点中存储一个max(区间最大值)不就能监视到区间当前还有无必要开根了吗
即当前区间中的最大值为1时就可以返回了
总结:
遍历一次线段树时间复杂度为n*logn,而最多开根6次就无需开根,即最多遍历6次完整的线段树就可以使得其区间最大值为1,故而使用剪枝后的时间复杂度为6*n*logn(与修改次数m无关了,6次之后进来即返回)
#include<iostream>
#include<cmath>
using namespace std;
typedef long long LL;
#define lc p<<1
#define rc p<<1|1
const int N = 1e5 + 10;
int n, m;
LL a[N];
struct node
{int l, r;LL max, sum;
}tr[N<<2];
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;tr[p].max = max(tr[lc].max, tr[rc].max);
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],a[l] };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);}void modify(int p, int x, int y)
{if (tr[p].max == 1)return;int l = tr[p].l, r = tr[p].r;if (l == r){tr[p].sum = sqrt(tr[p].sum);tr[p].max = sqrt(tr[p].max);return;}int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y);if (y > mid)modify(rc, x, y);pushup(p);
}
LL query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].sum;int mid = (l + r) / 2; LL ret = 0;if (x <= mid)ret += query(lc,x,y);if (y > mid)ret += query(rc, x, y);return ret;}
int main()
{cin >> n;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);cin >> m;for (int i = 1; i <= m; i++){int op, x, y; cin >> op >> x >> y;if (x > y)swap(x, y);if (op == 0){modify(1, x, y);}else{cout<<query(1, x, y)<<endl;}}}
权值线段树+离散化
P1908 逆序对 - 洛谷
上面注释掉的两种方法是分治思想实现的:全队是基于归并排序
而今天讲的线段树方法是一遍查询区间,一边更新线段树
#include<iostream>//时间复杂度为n*logn^2(sort的缘故)
#include<algorithm>
using namespace std;
// const int N = 5e5 + 10;
// int a[N];
// int tmp[N];
// long long ret;
//void dfs(int left, int right)
//{
// if (left == right)return;
// if (right - left == 1)
// {
// if (a[left] > a[right])ret += 1;
// return;
// }
// int mid = (left + right) / 2;
// dfs(left, mid);
// dfs(mid + 1, right);
//
// sort(a + left, a + mid + 1);
// sort(a + mid + 1, a + right + 1);
// int start1 = left, end1 = mid;
// int start2 = mid + 1, end2 = right;
// while (start1 <= end1 && start2 <= end2)
// {
// if (a[start1] > a[start2])
// {
// ret += end1 - start1 + 1;
// start2++;
// }
// else
// {
// start1++;
// }
// }
//}
//int main()
//{
// int n; cin >> n;
// for (int i = 1; i <= n; i++)cin >> a[i];
//
// dfs(1, n);
// cout << ret << endl;
//}// void merge(int left, int right)
// {
// if (left >= right)return;// int mid = (left + right) / 2;
// merge(left, mid);
// merge(mid + 1, right);// int start1 = left, end1 = mid;
// int start2 = mid + 1, end2 = right;
// int i = left;
// while (start1 <= end1 && start2 <= end2)
// {
// if (a[start1] > a[start2])
// {
// ret += end1 - start1 + 1;
// tmp[i++] = a[start2];
// start2++;
// }
// else
// {
// tmp[i++] = a[start1];
// start1++;
// }
// }
// while (start1 <= end1)tmp[i++] = a[start1++];
// while (start2 <= end2)tmp[i++] = a[start2++];
// for (int j = left; j <= right; j++)a[j] = tmp[j];// }
// int main()
// {
// int n; cin >> n;
// for (int i = 1; i <= n; i++)cin >> a[i];// merge(1, n);
// cout << ret << endl;
// }typedef long long LL;
#include<unordered_map>
#define lc p<<1
#define rc p<<1|1
const int N = 5e5 + 10;
int a[N], t[N];
int n, pos;//pos来表示离散化后的最大的数
unordered_map<int, int>mp;
struct node
{int l, r;LL cnt;
}tr[N<<2];
void pushup(int p)
{tr[p].cnt = tr[lc].cnt + tr[rc].cnt;
}
void build(int p,int l,int r)
{tr[p] = { l,r,0 };if (l == r)return;int mid = (l + r) >> 1;build(lc, l, mid); build(rc, mid + 1, r);//pushup(p); 此题此处不用
}
LL query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].cnt;int mid = (l + r) / 2; LL ret = 0;if (x <= mid)ret += query(lc, x, y);if (y > mid)ret += query(rc, x, y);return ret;
}
void modify(int p, int x)
{int l = tr[p].l, r = tr[p].r;if (l==r){tr[p].cnt++; return;}int mid = (l + r) / 2;if (x <= mid)modify(lc, x);else modify(rc, x);pushup(p);
}
int main()
{cin >> n;for (int i = 1; i <= n; i++){cin >> a[i]; t[i] = a[i];}//离散化sort(t + 1, t + n + 1);for (int i = 1; i <= n; i++){if (mp.count(t[i]))continue;mp[t[i]] = ++pos;}build(1, 1, pos);LL ret = 0;for (int i = 1; i <= n; i++){int x = mp[a[i]];ret+=query(1, x + 1, pos);modify(1, x);}cout << ret << endl;
}
线段树+数学
P5142 区间方差 - 洛谷
#define lc p<<1
#define rc p<<1|1
const int N = 1e5 + 10, mod = 1e9 + 7;
int n, m;
LL a[N];//很扯淡,这儿必须用LL
struct node
{int l, r;LL sum, qsum;
}tr[N<<2];
LL qpow(LL a, LL b, LL p)
{LL ret = 1;while (b){if (b & 1) ret = ret * a % p;a = a * a % p;b >>= 1;}return ret;
}
void pushup(node& p, node& l, node& r)
{//p.l = l.l, p.r = r.r;p.qsum = (l.qsum + r.qsum) % mod;p.sum = (l.sum + r.sum) % mod;
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],a[l] * a[l] % mod };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(tr[p], tr[lc], tr[rc]);
}
void modify(int p, int x, LL y)//将第x个节点的值改为y
{int l = tr[p].l, r = tr[p].r;if (l == r){tr[p].sum = y;tr[p].qsum = y * y % mod;return;}int mid = (l + r) / 2;if (x <= mid)modify(lc, x, y);else modify(rc, x, y);pushup(tr[p], tr[lc], tr[rc]);
}
node query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p];int mid = (l + r) / 2; if (y <= mid)return query(lc, x, y);else if (x > mid)return query(rc, x, y);node t, L = query(lc, x, y), R = query(rc, x, y);pushup(t, L, R);return t;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);while (m--){int op, x, y; cin >> op >> x >> y;if (op == 1){modify(1, x, y);}else{node t = query(1, x, y);LL sum = t.sum, qsum = t.qsum, len = (y-x+1);LL inv = qpow(len, mod - 2, mod);LL A = sum * inv % mod;LL D = qsum * inv % mod - A * A % mod;D = (D % mod + mod) % mod;cout << D << endl;}}
}
P10463 Interval GCD - 洛谷(区间修改(无法懒标记以及剪枝)--利用差分改为单点修改)
有这个结论,我们就可以维护原序列差分序列中的最大公约数,此时区间修改就变成的两次单点修改.
但是,在求差分序列 的最⼤公约数时除了区间的gcd值,还需要知道原数列的值。(可以在差分序列中维护⼀个区间和query1,此时原数列的值就是差分序列中区间的和。因为gcd区间【l+1,r】和差分和区间【1,l】不同,所以写了两个query,省的返回结构体
注意⽤差分解决问题时,最⼤公约数会出现负数的情况。注意取绝对值
#include<iostream>
using namespace std;
typedef long long LL;
#define lc p<<1
#define rc p<<1|1const int N = 5e5 + 10;
LL n, m;
struct node
{int l, r;LL sum, gcd;
}tr[N<<2];
LL gcd(LL a, LL b)
{return b == 0 ? a : gcd(b, a % b);
}
LL a[N];//差分数组 注意越界情况
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;tr[p].gcd = gcd(tr[lc].gcd, tr[rc].gcd);
}
void build(int p, int l, int r)
{tr[p] = { l,r,a[l],a[l] };if (l == r)return;int mid = (l + r) / 2;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);
}
void modify(int p, int x, LL k)
{int l = tr[p].l, r = tr[p].r;if (l == r){tr[p].sum += k;tr[p].gcd += k;return;}int mid = (l + r) / 2;if (x <= mid)modify(lc, x, k);else modify(rc, x, k);pushup(p);
}
LL query1(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].sum;int mid = (l + r) / 2; LL ret = 0;if (x <= mid)ret += query1(lc, x, y);if (y > mid)ret += query1(rc, x, y);return ret;}
LL query2(int p,int x,int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && y >= r)return tr[p].gcd;int mid = (l + r) / 2; LL g = 0;if (x <= mid)g = gcd(query2(lc, x, y),g);if (y > mid)g = gcd(query2(rc, x, y), g);return g;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++){LL x; cin >> x;a[i] += x; a[i + 1] -= x;}build(1, 1, n);while (m--){char ch; cin >> ch;if (ch == 'C'){int l, r; LL d; cin >> l >> r >> d;modify(1, l, d);if(r+1<=n)modify(1, r + 1, -d);}else{int x, y; cin >> x >> y;LL sum = query1(1, 1, x);//sum表示第x个元素的值(差分累加值)LL g = 0;if(x+1<=y) g = query2(1, x + 1, y);LL ret = gcd(sum, g);cout << abs(ret) << endl;}}return 0;
}