atcoder ABC 457 题解

A - Array

题目描述

给定一个长度为 NNN 的序列 A=(A1,A2,⋯ ,AN)A = (A_1, A_2, \cdots , A_N)A=(A1,A2,⋯,AN)。

随后给定一个在 111 到 NNN 之间(包括端点)的整数 XXX。

输出 AXA_XAX 的值。

解题思路

题目要求输出序列 AAA 中第 XXX 个元素的值,而题目已经保证 1≤X≤N1 \leq X \leq N1≤X≤N,所以我们只需要把数组存起来,然后直接按下标访问即可。注意到题目中数组的下标是从 111 开始的(A1A_1A1 表示第一个元素),因此在实现时我们将数组的下标也设为从 111 开始,这样就能和题目描述一一对应,不需要额外的下标转换操作。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 105;
int a[maxn];

int main()
{
	int n;
	cin >> n;
	// 读取数组元素,下标从 1 开始以匹配题目描述
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	// 读取要查询的下标 X
	int x;
	cin >> x;
	// 直接通过下标访问并输出对应元素
	cout << a[x];
	return 0;
}

B - Arrays

题目描述

给定 NNN 个序列 A1,A2,...,ANA_1, A_2, \ldots, A_NA1,A2,...,AN。

序列 AiA_iAi 的长度为 LiL_iLi,且 Ai=(Ai,1,Ai,2,...,Ai,Li)A_i = (A_{i,1}, A_{i,2}, \ldots, A_{i,L_i})Ai=(Ai,1,Ai,2,...,Ai,Li)。

随后给定整数 XXX 和 YYY,输出 AX,YA_{X,Y}AX,Y 的值。

解题思路

本题是 A 题的二维扩展,由一维数组变成了多个一维数组。观察到 YYY 是从 000 开始计数的(Ai,0A_{i,0}Ai,0 表示第一个元素),而 C++ 中的 vector 下标也是从 000 开始,因此我们直接将输入存入 vector 中,然后通过 a[X][Y-1] 来访问目标元素(因为 YYY 需要减一才能对应到 C++ 的下标)。

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 2e5 + 5;
vector<vector<int>> a;

int main()
{
	int n;
	cin >> n;
	// 调整 a 的大小以存储 N 个序列
	a.resize(n + 1);
	// 分别读取每个序列的元素
	for (int i = 1; i <= n; i++)
	{
		int l;
		cin >> l;
		for (int j = 0; j < l; j++)
		{
			int x;
			cin >> x;
			a[i].push_back(x);
		}
	}
	// 读取要查询的序列编号 X 和元素下标 Y
	int x, y;
	cin >> x >> y;
	// 由于题目中 Y 从 0 开始计数,需要减一转换为 C++ 的下标
	cout << a[x][y - 1];
	return 0;
}

C - Long Sequence

题目描述

给定整数 NNN 和 KKK,以及 NNN 个整数序列 A1,A2,...,ANA_1, A_2, \ldots, A_NA1,A2,...,AN 和长度为 NNN 的整数序列 C=(C1,C2,...,CN)C = (C_1, C_2, \ldots, C_N)C=(C1,C2,...,CN)。序列 AiA_iAi 的长度为 LiL_iLi,且 Ai=(Ai,1,Ai,2,...,Ai,Li)A_i = (A_{i,1}, A_{i,2}, \ldots, A_{i,L_i})Ai=(Ai,1,Ai,2,...,Ai,Li)。题目保证 1≤K≤∑i=1NCiLi1 \le K \le \sum_{i=1}^N C_iL_i1≤K≤∑i=1NCiLi。

按照以下过程从 AAA 和 CCC 构造整数序列 B=(B1,B2,...,B∑i=1NCiLi)B = (B_1, B_2, \ldots, B_{\sum_{i=1}^N C_iL_i})B=(B1,B2,...,B∑i=1NCiLi):

  • 令 BBB 为长度为 000 的整数序列。
  • 对于 i=1,2,...,Ni = 1, 2, \ldots, Ni=1,2,...,N 按顺序执行以下操作:
    • 执行 CiC_iCi 次:将 AiA_iAi 追加到 BBB 的末尾。

求出 BKB_KBK 的值。

解题思路

本题如果真的构造出整个序列 BBB 会超出时间限制,因为 BBB 的长度可能达到 101310^{13}1013 级别。关键在于观察:BBB 是由各个 AiA_iAi 依次重复 CiC_iCi 次拼接而成的,因此我们只需要定位 BKB_KBK 落在哪个 AiA_iAi 中即可。

具体做法是从第一个序列开始遍历,维护一个变量 cur 指向当前处理的序列。设当前序列 AcurA_{cur}Acur 的长度为 LLL,重复次数为 CcurC_{cur}Ccur,则该序列在 BBB 中占据的长度为 L×CcurL \times C_{cur}L×Ccur。如果 KKK 小于等于这个长度,说明答案就在当前序列中,直接输出即可;如果 KKK 更大,则将 KKK 减去这个长度,继续处理下一个序列。

由于 AiA_iAi 中的元素是依次重复的,BKB_KBK 在 AiA_iAi 中的位置可以通过 (K−1) mod Li(K-1) \bmod L_i(K−1)modLi 来计算。遍历的复杂度为 O(N)O(N)O(N),时间上完全可行。

cpp 复制代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

vector<vector<int>> a;
vector<int> b, c;
int n, k;

signed main()
{
	cin >> n >> k;
	// 调整容器大小以存储 N 个序列
	a.resize(n);
	// 读取各个序列 A_i 的元素
	for (int i = 0; i < n; i++)
	{
		int l;
		cin >> l;
		for (int j = 1; j <= l; j++)
		{
			int x;
			cin >> x;
			a[i].push_back(x);
		}
	}
	// 读取序列 C,记录每个 A_i 需要重复的次数
	for (int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		c.push_back(x);
	}
	// 从第一个序列开始遍历,定位 B_K 落在哪个序列中
	int cur = 0;
	while (k && cur < n)
	{
		// 当前序列在 B 中占据的长度
		if (k <= a[cur].size() * c[cur])
		{
			// 答案在当前序列中,通过取模确定具体位置
			cout << a[cur][(k - 1) % a[cur].size()];
			return 0;
		}
		// K 不在当前序列,减去当前序列占据的长度后继续向后查找
		k -= a[cur].size() * c[cur];
		cur++;
	}
	return 0;
}

D - Raise Minimum

题目描述

给定一个长度为 NNN 的序列 A=(A1,A2,...,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,...,AN) 和整数 KKK。

你可以执行 000 到 KKK 次(包括端点)以下操作:

  • 选择满足 1≤i≤N1 \le i \le N1≤i≤N 的整数 iii,并将 iii 加到 AiA_iAi 上。

求执行操作后序列的 min⁡1≤i≤NAi\displaystyle \min_{1 \le i \le N} A_i1≤i≤NminAi 的最大可能值。

解题思路

本题要求通过若干次操作使得序列的最小值最大化。观察操作的特点:每次选择位置 iii,可以将 AiA_iAi 增加 iii。这意味着位置 iii 每次增加的量是固定的 iii,而且我们最多执行 KKK 次操作。

对于给定的目标值 xxx,我们可以检查是否能够在 KKK 次操作内将所有元素提升到至少 xxx。对于位置 iii,如果 Ai<xA_i < xAi<x,需要增加 x−Aix - A_ix−Ai 才能达到目标。由于每次操作最多增加 iii,因此至少需要 ⌈(x−Ai)/i⌉\lceil (x - A_i) / i \rceil⌈(x−Ai)/i⌉ 次操作才能将 AiA_iAi 提升到 xxx。对所有位置求和得到总操作次数,如果不超过 KKK 则说明 xxx 是可达的。

基于上述判断函数,我们可以对 xxx 进行二分搜索。搜索的下界可以是 111,上界则可以是 A1+K+1A_1 + K + 1A1+K+1(最极端情况下全给 A1A_1A1 增加值)。每次取中点检查,如果可达则左边界移动,否则右边界移动,直到左右边界相差 111 为止。最终左边界即为最小值的最大可能值。使用 __int128 是因为计算过程中乘积可能超出 long long 范围。

cpp 复制代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

int n, k;
vector<int> A;

// 检查是否能在 K 次操作内将所有元素提升到至少 x
bool check(int x)
{
	__int128 cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		if (A[i] < x)
		{
			// 需要 (x - A[i]) / i 次操作,向上取整
			cnt += ( (__int128)x - A[i] + i - 1 ) / i;
			// 如果已超过 K 次则直接返回 false
			if (cnt > k) return false;
		}
	}
	return cnt <= k;
}

signed main()
{
	cin >> n >> k;
	A.resize(n + 1);
	for (int i = 1; i <= n; i++)
		cin >> A[i];

	// 二分搜索上界,最极端情况下全给 A[1] 增加值
	__int128 right = (__int128)A[1] + k + 1;
	int left = 1;

	while (right - left > 1)
	{
		int mid = (left + right) / 2;
		if (check(mid))
			left = mid;
		else
			right = mid;
	}

	cout << left << endl;
	return 0;
}

E - Crossing Table Cloth

题目描述

有 NNN 个单元格排成一行。从左数第 iii 个单元格 (1≤i≤N)(1 \le i \le N)(1≤i≤N) 称为单元格 iii。

有 MMM 块布。第 iii 块布 (1≤i≤M)(1 \le i \le M)(1≤i≤M) 覆盖从 LiL_iLi 到 RiR_iRi 的单元格。

回答 QQQ 个查询。对于第 qqq 个查询 (1≤q≤Q)(1 \le q \le Q)(1≤q≤Q),给定整数 SqS_qSq 和 TqT_qTq,请回答以下问题:

  • 判断是否可以从 MMM 块布中恰好选择两块并铺设,使得满足以下条件:
    • 单元格 SqS_qSq 到 TqT_qTq 被至少一块布覆盖,且没有其他单元格被任何布覆盖。

解题思路

本题要求判断是否能用恰好两块布完全覆盖指定区间 [Sq,Tq][S_q, T_q][Sq,Tq]。关键在于理解"恰好两块布"和"完全覆盖"这两个条件。

首先,如果存在完全相同的 [Li,Ri][L_i, R_i][Li,Ri] 等于 [L,R][L, R][L,R] 的布至少两块,那么显然可以用这两块布覆盖目标区间。如果只有一块完全相同的布,我们需要寻找其他方案。

考虑将覆盖区间分为左右两部分:让一块布负责覆盖区间的左半部分,另一块布负责右半部分。两块布的覆盖范围在中间可以重叠一个单元格,但不能再有其他空隙或延伸到区间之外。因此,我们需要找到一块以 LLL 为左端点的布,以及一块以 RRR 为右端点的布,使得它们的覆盖范围能够恰好(可以重叠)覆盖 [L,R][L, R][L,R]。

具体实现时,我们预处理两组信息:l[i] 记录所有左端点为 iii 的布的右端点集合,r[i] 记录所有右端点为 iii 的布的左端点集合。对于每个查询,先检查是否满足两块完全相同的情况,然后检查是否存在可以分割覆盖的方案。分割覆盖的条件是:存在一块以 LLL 为左端点的布,其右端点至少为 R−1R-1R−1,同时存在一块以 RRR 为右端点的布,其左端点至多为 L+1L+1L+1。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int n, m;
	cin >> n >> m;
	// l[i] 存储所有左端点为 i 的布的右端点
	// r[i] 存储所有右端点为 i 的布的左端点
	vector<vector<int>> l(n + 2);
	vector<vector<int>> r(n + 2);
	// cnt 用于计数完全相同的布的数量
	map<pair<int, int>, int> cnt;
	vector<pair<int, int>> lr;
	// 读取每块布的信息
	for (int i = 0; i < m; ++i)
	{
		int L, R;
		cin >> L >> R;
		l[L].push_back(R);
		r[R].push_back(L);
		lr.emplace_back(L, R);
		cnt[{L, R}]++;
	}
	// 对 l 和 r 中的右端点/左端点集合进行排序,便于二分查找
	for (int i = 1; i <= n; ++i)
	{
		sort(l[i].begin(), l[i].end());
		sort(r[i].begin(), r[i].end());
	}
	sort(lr.begin(), lr.end());
	// mi[i] 记录从位置 i 开始的最小的右端点
	vector<int> mi(n + 2, (int)1e9);
	int now = (int)1e9;
	for (int i = m - 1; i >= 0; --i)
	{
		mi[lr[i].first] = min(now, lr[i].second);
		now = mi[lr[i].first];
	}
	// 从后向前递推,确保 mi[i] 是从 i 到 n 的最小右端点
	for (int i = n - 1; i >= 1; --i)
	{
		mi[i] = min(mi[i], mi[i + 1]);
	}
	int q;
	cin >> q;
	while (q--)
	{
		int L, R;
		cin >> L >> R;
		// 如果有两块完全相同的布,可以直接覆盖
		if (cnt[{L, R}] >= 2)
		{
			cout << "Yes\n";
			continue;
		}
		if (L >= n)
		{
			cout << "No\n";
			continue;
		}
		// 检查是否存在分割覆盖的方案
		if (cnt[{L, R}] == 1 && (mi[L] < R || mi[L + 1] <= R))
		{
			cout << "Yes\n";
			continue;
		}
		// 查找以 L 为左端点的布中,右端点最大的那块
		bool l_is = !l[L].empty();
		int l_max = -1;
		if (l_is)
		{
			auto it = lower_bound(l[L].begin(), l[L].end(), R);
			if (it != l[L].begin())
			{
				--it;
				l_max = *it;
			}
			else
			{
				l_max = -1;
			}
		}
		// 查找以 R 为右端点的布中,左端点最小的那块
		bool r_is = !r[R].empty();
		int r_min = (int)1e9;
		if (r_is)
		{
			auto it = upper_bound(r[R].begin(), r[R].end(), L);
			if (it != r[R].end())
			{
				r_min = *it;
			}
			else
			{
				r_min = (int)1e9;
			}
		}
		// 判断两块布是否能覆盖 [L, R] 区间(允许重叠一个单元格)
		if (l_is && r_is && l_max >= r_min - 1)
		{
			cout << "Yes\n";
		}
		else
		{
			cout << "No\n";
		}
	}
	return 0;
}

F - Second Gap

题目描述

给定一个整数 NNN 和一个长度为 N−1N-1N−1 的整数序列 D=(D1,D2,...,DN−1)D = (D_1, D_2, \ldots, D_{N-1})D=(D1,D2,...,DN−1)。

求满足以下条件的排列 P=(P1,P2,...,PN)P = (P_1, P_2, \ldots, P_N)P=(P1,P2,...,PN)(1,2,...,N1, 2, \ldots, N1,2,...,N 的排列)的数量,模 998244353998244353998244353:

  • 对于每个 1≤i≤N−11 \le i \le N-11≤i≤N−1,设 PaP_aPa 和 PbP_bPb 为 (Pi,Pi+1,...,PN)(P_i, P_{i+1}, \ldots, P_N)(Pi,Pi+1,...,PN) 中最大和第二大的元素,则 ∣a−b∣=Di|a - b| = D_i∣a−b∣=Di。

解题思路

本题要求构造一个排列,使得后缀中最大和第二大元素的下标距离恰好等于对应的 DiD_iDi。从后向前考虑会更加方便,因为当我们处理位置 iii 时,位置 i+1i+1i+1 到 NNN 的排列已经确定。

从后往前递推时,第 iii 个位置需要满足 ∣i−(i+Di)∣=Di|i - (i + D_i)| = D_i∣i−(i+Di)∣=Di 这个条件,这意味着第 iii 个位置的最大值必须放在 i+Dii + D_ii+Di 处。设 valvalval 为位置 i+Dii + D_ii+Di 处的值,我们需要将 valvalval 放到位置 iii 作为后缀的最大值,然后将 111 到 val−1val-1val−1 这些较小的值分配到其他位置。

具体实现使用线段树来维护每个位置可以放置的值的数量。对于每个位置 iii,我们需要计算从 iii 到 nnn 这个后缀中已经放置的最大值 valvalval,然后根据 DiD_iDi 和 Di+1D_{i+1}Di+1 的关系来决定如何更新其他位置的可选值数量。当 Di≠Di+1D_i \neq D_{i+1}Di=Di+1 时,其他所有位置都只能放置比 valvalval 小的值;当 Di=Di+1D_i = D_{i+1}Di=Di+1 时,除了 iii 和 i+Dii + D_ii+Di 之外的位置可以放置 111 到 val−1val-1val−1 中的任意值。线段树支持区间赋值、区间乘法加法以及单点查询等操作,从而高效地完成动态规划的维护。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define MOD 998244353
const int maxn = 200005;

// 线段树节点标记:tree存储区间和,add为加法标记,mul为乘法标记,edt为赋值标记(-1表示无赋值)
ll tree[4 * maxn];
ll add[4 * maxn];
ll mul[4 * maxn];
ll edt[4 * maxn];
int di[maxn];
int n;

// 向下推送懒标记,确保子区间的值正确
void push(int node, int l, int r)
{
	if (edt[node] != -1)
	{
		int mid = (l + r) / 2;
		int left = 2 * node, right_node = 2 * node + 1;
		tree[left] = edt[node] * (mid - l + 1) % MOD;
		edt[left] = edt[node];
		add[left] = 0;
		mul[left] = 1;
		tree[right_node] = edt[node] * (r - mid) % MOD;
		edt[right_node] = edt[node];
		add[right_node] = 0;
		mul[right_node] = 1;
		edt[node] = -1;
	}
	if (mul[node] != 1 || add[node] != 0)
	{
		int mid = (l + r) / 2;
		int left = 2 * node, right_node = 2 * node + 1;
		tree[left] = (tree[left] * mul[node] + add[node] * (mid - l + 1)) % MOD;
		add[left] = (add[left] * mul[node] + add[node]) % MOD;
		mul[left] = mul[left] * mul[node] % MOD;
		tree[right_node] = (tree[right_node] * mul[node] + add[node] * (r - mid)) % MOD;
		add[right_node] = (add[right_node] * mul[node] + add[node]) % MOD;
		mul[right_node] = mul[right_node] * mul[node] % MOD;
		mul[node] = 1;
		add[node] = 0;
	}
}

// 区间更新操作,支持加法、乘法和赋值三种标记
void update_range(int node, int l, int r, int ul, int ur, ll add_val, ll mul_val, ll edt_val)
{
	if (ur < l || ul > r)
		return;
	if (ul <= l && r <= ur)
	{
		if (edt_val != -1)
		{
			tree[node] = edt_val * (r - l + 1) % MOD;
			edt[node] = edt_val;
			add[node] = 0;
			mul[node] = 1;
		}
		else
		{
			tree[node] = (tree[node] * mul_val + add_val * (r - l + 1)) % MOD;
			add[node] = (add[node] * mul_val + add_val) % MOD;
			mul[node] = mul[node] * mul_val % MOD;
		}
		return;
	}
	push(node, l, r);
	int mid = (l + r) / 2;
	update_range(2 * node, l, mid, ul, ur, add_val, mul_val, edt_val);
	update_range(2 * node + 1, mid + 1, r, ul, ur, add_val, mul_val, edt_val);
	tree[node] = (tree[2 * node] + tree[2 * node + 1]) % MOD;
}

// 单点查询,返回指定位置的当前值
ll query_pos(int node, int l, int r, int pos)
{
	if (l == r)
		return tree[node];
	push(node, l, r);
	int mid = (l + r) / 2;
	if (pos <= mid)
		return query_pos(2 * node, l, mid, pos);
	return query_pos(2 * node + 1, mid + 1, r, pos);
}

// 区间求和查询
ll query_sum(int node, int l, int r, int ql, int qr)
{
	if (qr < l || ql > r)
		return 0;
	if (ql <= l && r <= qr)
		return tree[node];
	push(node, l, r);
	int mid = (l + r) / 2;
	return (query_sum(2 * node, l, mid, ql, qr) + query_sum(2 * node + 1, mid + 1, r, ql, qr)) % MOD;
}

// 初始化线段树,末尾两个位置初始化为1(表示可以选择值1)
void init()
{
	for (int i = 0; i < 4 * maxn; ++i)
	{
		tree[i] = 0;
		add[i] = 0;
		mul[i] = 1;
		edt[i] = -1;
	}
	update_range(1, 1, n, n - 1, n - 1, 1, 1, -1);
	update_range(1, 1, n, n, n, 1, 1, -1);
}

// 从后向前递推,根据 D_i 和 D_{i+1} 的关系更新各位置的可选值数量
void solve()
{
	scanf("%d", &n);
	for (int i = 1; i < n; ++i)
	{
		scanf("%d", &di[i]);
	}
	init();

	for (int i = n - 2; i >= 1; --i)
	{
		// 取出位置 i + D_i 处的值作为当前后缀的最大值
		ll val = query_pos(1, 1, n, i + di[i]);
		if (di[i] != di[i + 1])
		{
			// D_i 与 D_{i+1} 不同时,其他位置只能放置比 val 小的值
			update_range(1, 1, n, 1, n, 0, 1, 0);
		}
		else
		{
			// D_i 与 D_{i+1} 相同时,根据 n-i-1 的值更新
			update_range(1, 1, n, 1, n, 0, (ll)(n - i - 1), -1);
		}
		// 将位置 i 和 i + D_i 固定为 val
		update_range(1, 1, n, i, i, val, 1, -1);
		update_range(1, 1, n, i + di[i], i + di[i], val, 1, -1);
	}

	// 输出所有位置的可选值数量之和
	printf("%lld\n", query_sum(1, 1, n, 1, n) % MOD);
}

int main()
{
	solve();
	return 0;
}

G - Catch All Apples

题目描述

有 NNN 个苹果落在数轴上。第 iii 个苹果在时间 TiT_iTi 落在坐标 XiX_iXi 处。

你可以在数轴上的任意位置放置一些机器人来收集所有 NNN 个苹果。机器人可以放置在任意坐标。

每个机器人从时间 000 开始运行,可以以速度至多 111 在数轴上自由移动。多个机器人可以在同一时刻占据相同坐标。如果机器人在时间 TiT_iTi 恰好处于坐标 XiX_iXi,则它可以收集第 iii 个苹果。

求收集所有苹果所需的最少机器人数量。

解题思路

本题要求计算机器人收集所有苹果所需的最少数量,关键在于将苹果的时空信息进行转换。对于每个苹果 (Ti,Xi)(T_i, X_i)(Ti,Xi),我们考虑两个组合:Ti+XiT_i + X_iTi+Xi 和 Ti−XiT_i - X_iTi−Xi。前者表示如果一直向右走能到达的最远距离,后者表示如果一直向左走能到达的最远距离。

观察发现,一个机器人如果在时间 TiT_iTi 处于 XiX_iXi,那么它必须满足 ∣Xi−x0∣≤Ti|X_i - x_0| \le T_i∣Xi−x0∣≤Ti,其中 x0x_0x0 是机器人的起始位置。这意味着机器人收集的苹果序列必须满足某种偏序关系。

我们按 Ti+XiT_i + X_iTi+Xi 从大到小对苹果排序,然后计算 Ti−XiT_i - X_iTi−Xi 的最长不上升子序列长度。具体来说,排序后我们需要找的是 Ti−XiT_i - X_iTi−Xi 的最长不下降子序列。设 d=Ti−Xid = T_i - X_id=Ti−Xi,问题转化为在序列 ddd 中找到一个最长的子序列,使得这个子序列单调不下降。这个子序列的长度就是所需的最少机器人数量。

为什么这样是对的呢?因为在按 Ti+XiT_i + X_iTi+Xi 排序后,对于子序列中的任意两个苹果 (Ta,Xa)(T_a, X_a)(Ta,Xa) 和 (Tb,Xb)(T_b, X_b)(Tb,Xb)(aaa 在 bbb 前面),如果 Ta−Xa≤Tb−XbT_a - X_a \le T_b - X_bTa−Xa≤Tb−Xb,那么机器人可以在收集完苹果 aaa 后赶到苹果 bbb 的位置。这正好满足机器人速度为 111 的约束。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll INF = 1e18;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	ll n;
	cin >> n;
	// vals[i][0] = t + x, vals[i][1] = t - x
	vector<array<ll, 2>> vals(n);
	for (ll i = 0; i < n; ++i)
	{
		ll t, x;
		cin >> t >> x;
		vals[i] = {t + x, t - x};
	}
	// 按 t + x 从大到小排序,t - x 从大到小排序
	sort(vals.begin(), vals.end(), [&](array<ll, 2> a, array<ll, 2> b)
		 {
        if (a[0] == b[0]) return a[1] > b[1];
        return a[0] > b[0]; });
	// dp 数组用于维护最长不下降子序列,dp[i] 表示长度为 i+1 的子序列的最小尾值
	vector<ll> dp(n + 1, INF);
	dp[0] = -INF;
	ll res = 1;
	for (ll i = 0; i < n; ++i)
	{
		ll d = vals[i][1];
		// 找到第一个大于等于 d 的位置,即为可接续的位置
		ll ind = lower_bound(dp.begin(), dp.end(), d) - dp.begin();
		dp[ind] = min(dp[ind], d);
		res = max(res, ind);
	}
	cout << res << "\n";
	return 0;
}
相关推荐
驭渊的小故事1 小时前
Java数据结构集合框架(栈(Stack)的详细解析)2000字详细解析
数据结构
小张成长计划..2 小时前
【C++】31:异常
c++
wenyi_leo2 小时前
豆包建议:C++ 学习资料
c++
特立独行的猫a2 小时前
C++轻量级UI库DuiLib使用指南与优劣解析
c++·ui
宵时待雨2 小时前
回溯算法专题1:递归
数据结构·c++·笔记·算法·leetcode·深度优先
小侯不躺平.2 小时前
C++ Boost库【3】 --类型推导
开发语言·c++
爱思德学术2 小时前
【SPIE出版】黄冈师范学院主办!第四届大数据、计算智能与应用国际会议(BDCIA 2026)
大数据·算法·数据分析·云计算·etl
洛水水2 小时前
【力扣100题】40.二叉树中的最大路径和
算法·leetcode·深度优先
洛水水2 小时前
【力扣100题】37.从前序与中序遍历序列构造二叉树
c++·算法·leetcode