最全线段树例题

例题1: 给一个长度n的序列, 对于每个询问 询问区间内最大连续和

 

一、一个区间的最大子段和有三种情况

1、左子区间的最大子段和

2、右子区间的最大子段和

3、横跨左右子区间

 

二、每个结点需要维护的信息

1、区间和sum

2、从区间左端点开始的最大子段和lgss

3、从区间右端点开始的最大子段和rgss

4、区间最大子段和gss

 

三、建树

1、遇到叶子节点就输入数据

2、合并区间信息

sum:左右子区间的区间和相加

lgss:max(左子区间的lgss,左子区间的sum + 右子区间的lgss)

rgss:max(右子区间的rgss,右子区间的sum + 左子区间的rgss)

gss:max(max(左子区间的最大子段和,右子区间的最大子段和),左子区间的右起最大子段和 + 右子区间的左起最大子段和)

const int maxn = 10005;
int a[maxn];
struct node {
	int sum, l, r, lmax, rmax, maxx;
}tree[maxn<<2];
node pushup(node a,node b) {
	node res;
	res.l = a.l;
	res.r = b.r;
	res.sum = a.sum + b.sum;
	res.lmax = max(a.lmax, a.sum + b.lmax);
	res.rmax = max(b.rmax, b.sum + a.rmax);
	res.maxx = max(max(a.maxx, b.maxx), a.rmax + b.lmax);
	return res;
}
void build(int l, int r, int rt) {
	tree[rt].l = l;
	tree[rt].r = r;
	if (l == r) {
		tree[rt].lmax = tree[rt].rmax = tree[rt].sum = tree[rt].maxx = a[l];
		return;
	}
	int mid= (l + r) >> 1;
	build(l, mid, rt <<1 );
	build(mid + 1, r, rt << 1 | 1);
	tree[rt]=pushup(tree[rt<<1],tree[rt<<1|1]);
}
node query(int L, int R, int rt) {
	if (L == tree[rt].l&&tree[rt].r == R) {
		return tree[rt];
	}
	int m = (tree[rt].l + tree[rt].r) >> 1;
	if (R <= m) return query(L, R, rt << 1);
	else if (L > m)  return query(L, R, rt << 1 | 1);
	else
		return pushup(query(L, m, rt << 1), query(m + 1, R, rt << 1 | 1));
}
int main() {
	int  n, q, c, b;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	build(1, n, 1);
	scanf("%d", &q);
	while (q--) {
		scanf("%d%d", &c, &b);
		printf("%d\n", query(c, b, 1).maxx);
	}
	return 0;
}

2.已知一开始有一个空序列,接下来有Q次操作,每次操作给出type、first和second三个值。当type为1时,意味着该操作属于第一种操作:往序列尾部添加first个second数。当type为2时,意味着该操作属于第二种操作:查询序列中第first小至第second小的数值之和(一共有(second - first + 1)个数被累加),并将结果对1000000007取模后输出。

 

分析:

sum: 区间数的值的和  num: 区间数的数量和  ,对于每次询问的first和second,我们找到第first个和第second个分别是哪两个数字,

如果两个数字不相同,那么就先算出分别取了多少个这两个数字,对于两个数字中间的数字和我们可以直接用线段树区间求和得到

 

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#pragma warning(disable:4996)
using namespace std;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
typedef long long ll;
struct node {
	ll x, y;
}e[maxn];
ll op[maxn];
ll b[maxn], sum[maxn<<2],num[maxn<<2];
ll num1[maxn];
void update(ll rt, ll l, ll r, ll x, ll ans) {
	if (l == r) {
		sum[rt] = (sum[rt] + (ans*b[x]) % mod) % mod;
		num[rt] += ans;
		return;
	}
	ll mid = (l + r) >> 1;
	if (x <= mid) {
		update(rt << 1, l, mid, x, ans);
	}
	else {
		update(rt << 1 | 1, mid + 1, r, x, ans);
	}
	sum[rt] = (sum[rt << 1] + sum[rt << 1 | 1]) % mod;
	num[rt] = (num[rt << 1] + num[rt << 1 | 1]);
}
ll query(ll L, ll R, ll l, ll r, ll rt) {//区间查询[L,R]的数的值的和
	if (L <= l && R >= r) {
		return sum[rt]%mod;
	}
	ll mid = (l + r) >> 1;
	ll ans = 0;
	if (L <= mid) {
		ans = (ans+query(L, R, l, mid, rt << 1))%mod;
	}
	if (mid < R) {
		ans = (ans+query(L, R, mid + 1, r, rt << 1 | 1))%mod;
	}
	return ans;
}
ll query1(ll pos, ll l, ll r, ll rt) {//查询pos位于b数组的哪个位置
	if (num[rt] >= pos && num[rt] - num1[r] < pos)
		return r;//说明第pos小的数是b[r]
	ll mid = (l + r) >> 1;
	if (pos <= num[rt << 1]) {
		query1(pos, l, mid, rt << 1);
	}
	else {
		query1(pos - num[rt << 1], mid + 1, r, rt << 1 | 1);
	}
}
ll query2(ll L, ll R, ll l, ll r, ll rt) {//求区间[L,R]有多少个数字
	if (L <= l && R >= r) {
		return num[rt];
	}
	ll mid = (l + r) >> 1;
	ll ans = 0;
	if (L <= mid) {
		ans += query2(L, R, l, mid, rt << 1);
	}
	if(mid<R) {
		ans += query2(L, R, mid + 1, r, rt << 1 | 1);
	}
	return ans;
}
int main() {
	int n;
	scanf("%d", &n);
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%lld%lld%lld", &op[i], &e[i].x, &e[i].y);
		if (op[i] == 1) {
			b[++cnt] = e[i].y;
		}
	}
	sort(b + 1, b + cnt + 1);
	for (int i = 1; i <= n; i++) {
		if (op[i] == 1) {
			ll m = lower_bound(b + 1, b + cnt + 1, e[i].y) - b;
			update(1, 1, cnt, m, e[i].x);
			num1[m] += e[i].x;
		}
		else {
			ll l = query1(e[i].x, 1, cnt, 1);
			ll r = query1(e[i].y, 1, cnt, 1);
			ll ans = 0;
			if (l == r) {
				ans = b[l] * (e[i].y - e[i].x + 1) % mod;
			}
			else {
				ll l = query1(e[i].x, 1, cnt, 1);
				ll r = query1(e[i].y, 1, cnt, 1);
				ll k1 = ((query2(1, l, 1, cnt, 1) - e[i].x + 1 + mod) % mod)*b[l] % mod;//l剩余的
				ll k2 = ((e[i].y - query2(1, r - 1, 1, cnt, 1) + mod) % mod)*b[r] % mod;///r剩余的
				ll k0 = query(l + 1, r - 1, 1, cnt, 1);//[l+1,r-1]整段区间的
				ans = (k1 + k2 + k0) % mod;
			}
			printf("%lld\n", ans);
		}
	}
	return 0;
}

给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:

1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。

2、“Q l r”,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。

对于每个询问,输出一个整数表示答案。

 

题解

gcd(a,b)=gcd(a,b−a)

gcd(a,b,c)=gcd(a,b−a,c−b)

gcd(a1,a2,⋯,an)=gcd(a1,a2−a1,a3−a2,⋯,an−an−1)

知道了这个性质我们可以开一个B[  ] 数组,表示原序列的差分序列,用线段数维护B的区间最大公约数

对于询问“Q l r”等于求出gcd(a[l],query(1,l+1,r))

对于询问"C l r d"等于B[l] 加上d ,B[r+1] 减去d ,只需两次线段数的单点修改即可,对于原序列中A 的值,我们可以用一个“区间修改+单点查询”的树状数组来维护

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#pragma warning(disable:4996)
using namespace std;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
typedef long long ll;
struct node {
	ll x, y;
}e[maxn];
ll op[maxn];
ll b[maxn], sum[maxn<<2],num[maxn<<2];
ll num1[maxn];
void update(ll rt, ll l, ll r, ll x, ll ans) {
	if (l == r) {
		sum[rt] = (sum[rt] + (ans*b[x]) % mod) % mod;
		num[rt] += ans;
		return;
	}
	ll mid = (l + r) >> 1;
	if (x <= mid) {
		update(rt << 1, l, mid, x, ans);
	}
	else {
		update(rt << 1 | 1, mid + 1, r, x, ans);
	}
	sum[rt] = (sum[rt << 1] + sum[rt << 1 | 1]) % mod;
	num[rt] = (num[rt << 1] + num[rt << 1 | 1]);
}
ll query(ll L, ll R, ll l, ll r, ll rt) {//区间查询[L,R]的数的值的和
	if (L <= l && R >= r) {
		return sum[rt]%mod;
	}
	ll mid = (l + r) >> 1;
	ll ans = 0;
	if (L <= mid) {
		ans = (ans+query(L, R, l, mid, rt << 1))%mod;
	}
	if (mid < R) {
		ans = (ans+query(L, R, mid + 1, r, rt << 1 | 1))%mod;
	}
	return ans;
}
ll query1(ll pos, ll l, ll r, ll rt) {//查询pos位于b数组的哪个位置
	if (num[rt] >= pos && num[rt] - num1[r] < pos)
		return r;//说明第pos小的数是b[r]
	ll mid = (l + r) >> 1;
	if (pos <= num[rt << 1]) {
		query1(pos, l, mid, rt << 1);
	}
	else {
		query1(pos - num[rt << 1], mid + 1, r, rt << 1 | 1);
	}
}
ll query2(ll L, ll R, ll l, ll r, ll rt) {//求区间[L,R]有多少个数字
	if (L <= l && R >= r) {
		return num[rt];
	}
	ll mid = (l + r) >> 1;
	ll ans = 0;
	if (L <= mid) {
		ans += query2(L, R, l, mid, rt << 1);
	}
	if(mid<R) {
		ans += query2(L, R, mid + 1, r, rt << 1 | 1);
	}
	return ans;
}
int main() {
	int n;
	scanf("%d", &n);
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%lld%lld%lld", &op[i], &e[i].x, &e[i].y);
		if (op[i] == 1) {
			b[++cnt] = e[i].y;
		}
	}
	sort(b + 1, b + cnt + 1);
	for (int i = 1; i <= n; i++) {
		if (op[i] == 1) {
			ll m = lower_bound(b + 1, b + cnt + 1, e[i].y) - b;
			update(1, 1, cnt, m, e[i].x);
			num1[m] += e[i].x;
		}
		else {
			ll l = query1(e[i].x, 1, cnt, 1);
			ll r = query1(e[i].y, 1, cnt, 1);
			ll ans = 0;
			if (l == r) {
				ans = b[l] * (e[i].y - e[i].x + 1) % mod;
			}
			else {
				ll l = query1(e[i].x, 1, cnt, 1);
				ll r = query1(e[i].y, 1, cnt, 1);
				ll k1 = ((query2(1, l, 1, cnt, 1) - e[i].x + 1 + mod) % mod)*b[l] % mod;//l剩余的
				ll k2 = ((e[i].y - query2(1, r - 1, 1, cnt, 1) + mod) % mod)*b[r] % mod;///r剩余的
				ll k0 = query(l + 1, r - 1, 1, cnt, 1);//[l+1,r-1]整段区间的
				ans = (k1 + k2 + k0) % mod;
			}
			printf("%lld\n", ans);
		}
	}
	return 0;
}

在i右侧的所有满足a[j]>=a[i]+m 中最大的j−i−1 ,若不存在这样的数则值为−1。输出每个元素的愤怒值。

 

思路:用线段树维护区间最大值。那么要查询的其实就是[i+1,n] 内满足a[j]>=a[i]+m 的最大的j,因此考虑优先查询右子树,最后返回下标即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 500005;
#pragma warning(disable:4996)
typedef long long ll;
struct node {
	ll l, r, maxx;
}tree[maxn];
ll a[maxn];
void build(int l, int r, int rt) {
	tree[rt].l = l;
	tree[rt].r = r;
	if (l == r) {
		tree[rt].maxx = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid,rt<<1);
	build(mid + 1, r, rt << 1 | 1);
	tree[rt].maxx = max(tree[rt << 1].maxx, tree[rt << 1 | 1].maxx);
}
ll query(int l, int r, int rt, ll v) {
	int mid = (tree[rt].l + tree[rt].r) >> 1;
	if (tree[rt].l == l && tree[rt].r == r) {
		if (tree[rt].maxx < v) {
			return -1;
		}
		if (tree[rt].l == tree[rt].r) {
			return l;
		}
		if (tree[rt << 1 | 1].maxx >= v) {
			return query(mid + 1, r, rt << 1 | 1, v);
		}
		else if (tree[rt << 1].maxx > v) {
			return query(l, mid, rt << 1, v);
		}
		else {
			return -1;
		}
	}
	if (r <= mid) {
		return query(l, r, rt << 1, v);
	}
	else if (l > mid) {
		return query(l, r, rt << 1 | 1, v);
	}
	else {
		ll ans1 = query(l, mid, rt << 1, v);
		ll ans2 = query(mid + 1, r, rt << 1 | 1, v);
		if (ans2 != -1) {
			return ans2;
		}
		else if (ans1 != -1) {
			return ans1;
		}
		else {
			return -1;
		}
	}
}
int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	build(1, n, 1);
	for (int i = 1; i <= n; i++) {
		if (i == n) {
			printf("-1\n");
		}
		else {
			ll ans = query(i + 1, n, 1, a[i]+m);
			if (ans != -1) {
				printf("%lld ", ans-i-1);
			}
			else {
				printf("-1 ");
			}
		}
	}
	return 0;
}

给你一个区间,要支持两种区间操作。

第一种操作是单点更新,第二种操作是询问某个区间是否可以去掉一个元素或者不去使这段区间的gcd为x 的倍数。

分析:

线段树来做,做更改时直接做,每个点存gcd,然后查询时,我们判断区间不满足时就判断到点,并让cnt++,如果cnt大于1直接return(只能有一个数字不是x的倍数)。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#pragma warning(disable:4996)
using namespace std;
typedef long long ll;
const int maxn = 300005;
int n;
int a[maxn];
int tree[maxn * 4];
int gcd(int x, int y) {
	if (y == 0)
		return x;
	else {
		return gcd(y, x%y);
	}
}
int cnt;
void pushup(int rt) {
	tree[rt] = gcd(tree[rt << 1], tree[rt << 1 | 1]);
}
void build(int l, int r, int rt) {
	if (l == r) {
		tree[rt] = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid, rt << 1);
	build(mid + 1, r, rt << 1 | 1);
	pushup(rt);
}
void update(int l, int r, int pos, int k, int rt) {
	if (l==r) {
		tree[rt] = k;
		return;
	}
	int mid = (l + r) >> 1;
	if (pos<=mid ) {
		update(l, mid, pos, k, rt << 1);
	}
	else {
		update(mid + 1, r, pos,k, rt << 1 | 1);
	}
	pushup(rt);
}
void query(int l, int r, int L, int R, int k, int rt) {
	if (cnt > 1)
		return;
	if (l == r) {
		cnt++;
		return;
	}
	int mid = (l + r) >> 1;
	if (L <= mid&&tree[rt<<1]%k!=0){
		query(l, mid, L, R, k, rt << 1);
	}
	if (R > mid&&tree[rt << 1 | 1] % k != 0) {
		query(mid + 1, r, L, R, k, rt << 1 | 1);
	}
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	build(1, n, 1);
	int m;
	scanf("%d", &m);
	while (m--) {
		int x, l, r, k;
		scanf("%d", &x);
		if (x == 2) {
			scanf("%d%d", &l, &k);
			update(1, n, l, k, 1);
		}
		else {
			scanf("%d%d%d", &l, &r, &k);
			cnt = 0;
			query(1, n, l, r, k, 1);
			if (cnt > 1) {
				printf("NO\n");
			}
			else {
				printf("YES\n");
			}
		}
	}
	return 0;
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值