【NOI2022省选挑战赛 Contest5 A】我的 OI(线段树)

这篇博客详细解析了如何使用线段树处理区间分段函数的问题,具体涉及NOI2022省选挑战赛Contest5A的题目。博主首先介绍了题目的背景和思路,指出可以将每个位置视为一个分段函数,然后通过两个指针扫描并合并这些函数。接着,他们提出了利用线段树和单调队列的概念来优化查询过程,确保在询问时能够快速得到结果。代码部分展示了C++实现的线段树数据结构和查询函数,用于高效处理区间内的分数变化。
摘要由CSDN通过智能技术生成

我的 OI

题目链接:NOI2022省选挑战赛 Contest5 A

题目大意

给你一个序列,然后每个位置有 a,b 两个值,如果你当前分数大于 a 就可以加上 b 的分数。
然后多组询问每次给你一开始的分数,起点和终点,然后问你从起点走到终点你的分数会变成多少。

思路

我们考虑先单独考虑一个位置,可以发现它可以表示成一个分段函数,两段。

然后你试着用线段树,然后想如何合并两段。
你可以发现你可以用两个指针扫,然后按着那个加贡献的基准(这个不是从当前位置,而是这个区间的左边之前)从小到大放,然后每个要把前面能有的贡献都要加上。
然后这么搞之后如果你把全部都放上,你会发现有一部分可能不是递增的,然后我们要弄个类似单调队列之类的因为毕竟是后面的贡献大。

然后在询问的时候,线段树可以把询问分成 log ⁡ \log log 个块,然后我们从前往后看每个块,然后可以用二分找到贡献的位置,然后每次贡献即可。

代码

#include<cstdio>
#include<vector>
#include<climits>
#include<cstring>
#include<algorithm>
#define ll long long

using namespace std;

const int N = 500000 + 100;
int n, q, a[N], b[N], l, r;
ll x;

struct XD_tree {
	vector <pair<ll, ll> > f[N << 2], tmp;
	
	void up(int now) {
		int x = 0, y = 0; f[now].clear(); tmp.clear();
		while (x < f[now << 1].size() && y < f[now << 1 | 1].size()) {
			if (f[now << 1][x].first + f[now << 1][x].second < f[now << 1 | 1][y].first) {
				tmp.push_back(make_pair(f[now << 1][x].first, f[now << 1][x].second + (y ? f[now << 1 | 1][y - 1].second : 0)));
				x++;
			}
			else {
				tmp.push_back(make_pair(f[now << 1 | 1][y].first - (x ? f[now << 1][x - 1].second : 0), f[now << 1 | 1][y].second + (x ? f[now << 1][x - 1].second : 0)));
				y++;
			}
		}
		while (x < f[now << 1].size()) {
			tmp.push_back(make_pair(f[now << 1][x].first, f[now << 1][x].second + (y ? f[now << 1 | 1][y - 1].second : 0)));
			x++;
		}
		while (y < f[now << 1 | 1].size()) {
			tmp.push_back(make_pair(f[now << 1 | 1][y].first - (x ? f[now << 1][x - 1].second : 0), f[now << 1 | 1][y].second + (x ? f[now << 1][x - 1].second : 0)));
			y++;
		}
		for (int i = 0; i < tmp.size(); i++) {
			while (f[now].size() && f[now][f[now].size() - 1].first >= tmp[i].first) f[now].pop_back();
			f[now].push_back(tmp[i]);
		}
	}
	
	void build(int now, int l, int r) {
		if (l == r) {
			f[now].push_back(make_pair(a[l] + 1, b[l])); return ;
		}
		int mid = (l + r) >> 1; build(now << 1, l, mid); build(now << 1 | 1, mid + 1, r);
		up(now);
	}
	
	void query(int now, int l, int r, int L, int R, ll &x) {
		if (L <= l && r <= R) {
			if (upper_bound(f[now].begin(), f[now].end(), make_pair(x, LLONG_MAX)) != f[now].begin()) 
				x += (*(upper_bound(f[now].begin(), f[now].end(), make_pair(x, LLONG_MAX)) - 1)).second;
			return ;
		}
		int mid = (l + r) >> 1;
		if (L <= mid) query(now << 1, l, mid, L, R, x);
		if (mid < R) query(now << 1 | 1, mid + 1, r, L, R, x);
	}
}T;

int main() {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	for (int i = 1; i <= n; i++) {
		scanf("%d", &b[i]);
	}
	
	T.build(1, 1, n);
	while (q--) {
		scanf("%d %d %lld", &l, &r, &x);
		T.query(1, 1, n, l, r, x);
		printf("%lld\n", x);
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值