2021牛客暑期多校训练营1 J Journey among Railway Stations

这篇博客介绍了如何使用线段树数据结构解决一条铁路线上站点时间规划的问题。文章详细阐述了题意,包括每个站点的进站时间和区间花费,并给出了三种操作:查询路径可行性、修改区间花费和更新进站时间。通过定义节点状态和巧妙地合并相邻节点,实现了线段树的更新和查询功能,从而高效地解决了这个问题。代码实现部分展示了AC的C++代码,展示了如何构建和操作线段树。
摘要由CSDN通过智能技术生成

J Journey among Railway Stations

题目链接

题意

一条线上存在n个站点,每个站点能够进站的时间为 [ u i , r i ] [u_i,r_i] [ui,ri],并且 i i i站点到 i + 1 i+1 i+1站点需要花费的时间为 c o s t i cost_i costi

如果列车在 u i u_i ui之前到达,需要等一段时间;如果列车在 v i v_i vi之后到达,则不能通过站点 i i i

有3中操作:

  • 给定两个值ij:求列车能否从 i i i站点到 j j j站点。
  • 给定两个值iw:将站点 i i i到站点 i + 1 i+1 i+1之间的花费 c o s t i cost_i costi修改为 w w w
  • 给定三个值ipq:将站点 i i i能够进站的时间修改为 [ p , q ] [p,q] [p,q]

思路

根据题意,可以看出当进站时间小于 u i u_i ui时,需要等到 u i u_i ui;当进站时间超过 v i v_i vi时,不合法。(如下图 f ( x ) f(x) f(x)

定义几个参数:

  • 最迟到达当前节点的时间:latest_in
  • 从当前节点出来最早的时间:earliest_out
  • 区间内的cost和:sum_cost
    在这里插入图片描述

注意:每个节点是这样一个函数 f ( x ) f(x) f(x),两个相邻的节点合并之后依旧满足这个类型的函数,所以可以用线段树来维护这样一个区间信息。

以下面数据为例说明:

5
1 2 3 4 5
3 4 5 6 7
1 1 1 1

站点1站点2进行合并:站点1的进站时间为 [ 1 , 3 ] [1,3] [1,3]​​,站点2的进站时间为 [ 2 , 3 ] [2,3] [2,3]​​。

在这里插入图片描述

合并后
在这里插入图片描述

这个是核心

  • latest_in = min(l.latest_in, r.latest_in - l.sum_cost):

    • 如果慢于l.max_t会赶不上左边的进站时间;

    • 如果慢于r.max_t-l.sum_c会赶不上右边的进站时间。

  • earliest_out = max(l.earliest_out + r.sum_cost, r.earliest_out)

    • l.earliest_out + r.sum_cost 左区间出来后最快能到右边的时间;
    • r.earliest_out 右区间最快出来的时间。
  • sum_cost = l.sum_cost + r.sum_cost

    • 两个区间的 cost 相加。

AC的代码

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10, inf = 0x3f3f3f3f;
int n, u[N], v[N], cost[N];

struct Node {
	int latest_in, earliest_out, sum_cost;
}node[N<<2];

Node operator +(Node l, Node r) {
	if (l.earliest_out > r.latest_in) {
		// 左区间最快的出来时间也赶不上右边的最迟到达时间
		// 无法从l到达r
		return { -1, inf, inf };
	}
	return {
		min(l.latest_in, r.latest_in - l.sum_cost),
		max(l.earliest_out + r.sum_cost, r.earliest_out),
		l.sum_cost + r.sum_cost
	};
}

#define cl (k*2)
#define cr (k*2+1)
#define mid ((l+r)/2)

void pushup(int k) {
	node[k] = node[cl] + node[cr];
}

// 很妙的写法,初始化+单点修改
void init(int x) {
	int k = 1, l = 1, r = n;
	while (l != r) {
		if (x <= mid) {
			k = cl; r = mid;
		}
		else {
			k = cr; l = mid + 1;
		}
	}
	node[k] = { v[x], min(u[x] + cost[x], inf), cost[x] };
	while (k >>= 1)
		pushup(k);
}

int ql, qr;
Node now;
// 向下递归
void dfs(int k, int l, int r) {
	if (ql <= l && qr >= r) {
		now = now + node[k];
		return;
	}
	if (ql <= mid)
		dfs(cl, l, mid);
	if (qr > mid)	
		dfs(cr, mid + 1, r);
}


bool query(int l, int r) {
	if (l == r)	return true;
	ql = l;
	qr = r - 1;

	now = { inf, 0, 0 };
	dfs(1, 1, n);
	return now.earliest_out <= v[r];
}


int main() {
	int t, Q;
	cin >> t;
	while (t--) {
		cin >> n;
		for (int i = 1; i <= n; i++)	cin >> u[i];
		for (int i = 1; i <= n; i++)	cin >> v[i];
		for (int i = 1; i < n; i++)		cin >> cost[i];
		for (int i = 1; i <= n; i++)	init(i);
		cin >> Q;
		while (Q--) {
			int ty;
			cin >> ty;
			if (ty == 0) {
				int l, r;
				cin >> l >> r;
				if (query(l, r)) cout << "Yes\n";
				else cout << "No\n";
			}
			else
				if (ty == 1) {
					int i, w;
					cin >> i >> w;
					cost[i] = w;
					init(i);
				}
				else {
					int i;
					cin >> i >> u[i] >> v[i];
					init(i);
				}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值