线段树算法详解与实现

线段树常见场景

节点最大个数为区间长度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|1

const 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|1

const 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值