信息学奥赛一本通 4150:【GESP2509七级】⾦币收集 | 洛谷 P14078 [GESP202509 七级] 金币收集

【题目链接】

ybt 4150:【GESP2509七级】⾦币收集
洛谷 P14078 [GESP202509 七级] 金币收集

【题目难度】:B

【题目考点】

1. 线性动规:求最长上升子序列
  • 动规求最长上升子序列,时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 贪心+二分求最长上升子序列,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

相关概念及方法见:ybt 1281:最长上升子序列

【解题思路】

随着时间 t t t的增大,角色只能向位置 x x x不变或更大的位置走。 t i t_i ti时刻到达 x i x_i xi才能获得一个金币。

为了方便理解,可以把时间 t t t当做直角坐标系的x轴,把位置 x x x当做y轴,每个可以获得金币的时刻和位置 ( t i , x i ) (t_i, x_i) (ti,xi)就是平面直角坐标系中的一个给定的点。角色从原点 ( 0 , 0 ) (0,0) (0,0)出发,时间 t t t每增加1,角色只能选择让位置 x x x不变或增加1,也就是只能向右移动一个位置( t t t增加1, x x x不变),或向右上方移动一个位置( t t t增加1, x x x增加1)。本题求从 ( 0 , 0 ) (0,0) (0,0)出发按照上述规则移动,可以经过最多的给定点的数量。

如果一个点 ( t , x ) (t,x) (t,x)满足 t < x t<x t<x,那么基本从 ( 0 , 0 ) (0,0) (0,0)出发每次都向右上移动,当横坐标等于 t t t时,纵坐标也小于 x x x,也就是无论如何都无法到达这样的点。因此在输入时,只保留满足 t ≤ x t\le x t≤x的点。

经过点的过程中,假设先经过第 j j j点,再经过第 i i i点。首先要满足 x i ≥ x j x_i\ge x_j xi≥xj。从第 j j j点走到第 i i i点所用的时间为 t i − t j t_i-t_j ti−tj,经过的路程为 x i − x j x_i-x_j xi−xj。每个单位时间最多走一个单位距离,因此 t i − t j t_i-t_j ti−tj时间内最多走过的路程为 t i − t j t_i-t_j ti−tj。因此实际走过的路程 x i − x j x_i-x_j xi−xj必须满足 x i − x j ≤ t i − t j x_i-x_j\le t_i-t_j xi−xj≤ti−tj,也可以写为 t i − x i ≥ t j − x j t_i-x_i\ge t_j-x_j ti−xi≥tj−xj。

因此,选择经过的点中如果第 i i i点是第 j j j点的下一个点,必须满足 t j ≤ t i , x i ≥ x j , t i − x i ≥ t j − x j t_j\le t_i, x_i\ge x_j, t_i-x_i\ge t_j-x_j tj≤ti,xi≥xj,ti−xi≥tj−xj

接下来要按顺序遍历各个点,看最多可以经过哪些点。问题在于:按什么顺序遍历各个点。可选的排序方法有两种:按 x x x升序顺序遍历各点,或按 t t t升序顺序遍历各点。

  • 如果按 t t t升序遍历各点,先访问并选择了第 j j j点,再访问第 i i i点,看是否可以选择第 i i i点。由于各点关于 t t t是升序的,因此直接满足了 t j ≤ t i t_j\le t_i tj≤ti,还需要再判断如果第 i i i点满足 x i ≥ x j x_i\ge x_j xi≥xj与 t i − x i ≥ t j − x j t_i-x_i\ge t_j-x_j ti−xi≥tj−xj,那么才可以选择第 i i i点,使选择点的序列中第 i i i点作为 j j j点的下一个点。

  • 如果按 x x x升序遍历各点,如果两个点的 x x x相同,也可以先经过 t t t较小的点,再经过 t t t较大的点,因此当 x x x相同时,要按 t t t从小到大排序。先访问并选择了第 j j j点,而后再访问第 i i i点,看是否可以选择第 i i i点。由于各点关于 x x x是升序的,因此已经满足 x i ≥ x j x_i\ge x_j xi≥xj,接下来只要判断满足 t i − x i ≥ t j − x j t_i-x_i\ge t_j-x_j ti−xi≥tj−xj,自然就满足了 t i ≥ t j t_i\ge t_j ti≥tj,因此那么就可以选择点 i i i作为选点序列中点 j j j的下一个点。

    已知 x i ≥ x j x_i\ge x_j xi≥xj,对于 t i − x i ≥ t j − x j t_i-x_i\ge t_j-x_j ti−xi≥tj−xj,不等式坐标加上较大的 x i x_i xi,右边加上较小的 x j x_j xj,不等式符号不变,得到 t i ≥ t j t_i\ge t_j ti≥tj。

    这种排序方法在判断能否选择点 i i i时减少了一个判断条件,更加简洁。因此选择对所有的点按 x x x升序排序, x x x相等时按 t t t升序排序。

对于排序后的点序列,要找到最长的子序列,子序列中相邻两点前面的第 j j j点和后面的第 i i i点要满足 t j − x j ≤ t i − x i t_j-x_j\le t_i-x_i tj−xj≤ti−xi。想要求选择点序列的最大长度
相当于存在序列 b b b, b i = t i − x i b_i=t_i-x_i bi=ti−xi,求 b b b序列的最长不降子序列的长度。

其方法等同于求最长上升子序列,相关概念及方法参考:ybt 1281:最长上升子序列,以下不再详细解释方法的原理。

解法1(非正解):动态规划求最长不降子序列

状态定义: d p i dp_i dpi表示 b b b序列前 i i i元素中最长不降子序列的长度。

状态转移方程: d p i = max ⁡ { d p j + 1 } , b j ≤ b i , 1 ≤ j < i dp_i =\max\{dp_j+1\}, b_j\le b_i, 1\le j<i dpi=max{dpj+1},bj≤bi,1≤j<i

求 d p dp dp数组的最大值,即为本题结果。

时间复杂度为 O ( n 2 ) O(n^2) O(n2)

解法2:贪心+二分

设 d d d序列, d i d_i di表示 b b b序列长为 i i i的所有不降子序列中,末尾元素最小的不降子序列的末尾元素。 d d d序列长为 l e n len len

  • 如果 b i ≥ d l e n b_i\ge d_{len} bi≥dlen,那么可以在长为 l e n len len的不降子序列末尾增加 b i b_i bi,得到长为 l e n + 1 len+1 len+1的不降子序列,末尾元素为 b i b_i bi, l e n len len增加1。
  • 如果 b i < d l e n b_i < d_{len} bi<dlen,由于 d d d序列是单调递增的,可以在 d d d序列中通过二分找到满足 d l − 1 ≤ b i < d l d_{l-1}\le b_i<d_l dl−1≤bi<dl位置 l l l,即位置 l l l是满足 d l > b i d_l>b_i dl>bi的最小下标。可以在长为 l − 1 l-1 l−1的末尾元素为 d l − 1 d_{l-1} dl−1的序列末尾添加 b i b_i bi,得到长为 l l l的末尾元素为 b i b_i bi的不降子序列,将 d l d_l dl更新为 b i b_i bi。

最后 l e n len len即为 b b b序列的最长不降子序列的长度。

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

解法2:离散化+树状数组

对于动规过程:

状态定义: d p i dp_i dpi表示 b b b序列前 i i i元素中最长不降子序列的长度。

状态转移方程: d p i = max ⁡ { d p j + 1 } , b j ≤ b i , 1 ≤ j < i dp_i =\max\{dp_j+1\}, b_j\le b_i, 1\le j<i dpi=max{dpj+1},bj≤bi,1≤j<i

先对 b b b序列做离散化(注意离散化后的最小值为1,不能为0。因为树状数组的下标最小为1)。而后设 c c c数组, c x c_x cx表示以 b b b数组值为 x x x的元素为结尾的最长上升子序列的长度。那么 = max ⁡ { d p j } , b j ≤ b i =\max\{dp_j\}, b_j\le b_i =max{dpj},bj≤bi,就是 c c c数组区间 [ 1 , b i ] [1,b_i] [1,bi]的最大值,该值加1就是 d p i dp_i dpi的值,而后更新 c b i = m a x ( c b i , d p i ) c_{b_i}=max(c_{b_i}, dp_i) cbi=max(cbi,dpi)。可以使用树状数组维护序列的区间最大值,只能求前缀最大值。

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【题解代码】

解法1(非正解):动态规划求最长上升子序列 56pt

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Pos
{
	int x, t;
	bool operator < (const Pos &b) const
	{
		return x < b.x || x == b.x && t < b.t;
	}
} a[N];
int an, n, b[N], dp[N], ans;//dp[i]:前i个点中选点包含第i点的最大选点数量 
int main()
{
	int x, t;
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> x >> t;
		if(t >= x)//只保留可能走到的点 
			a[++an] = Pos{x, t};
	}
	sort(a+1, a+1+an);
	for(int i = 1; i <= an; ++i)
		b[i] = a[i].t-a[i].x;
	for(int i = 1; i <= an; ++i) 
	{
		dp[i] = 1;
		for(int j = 1; j < i; ++j) if(b[j] <= b[i])
			dp[i] = max(dp[i], dp[j]+1);
		ans = max(ans, dp[i]);
	}
	cout << ans;
	return 0;
}

解法2:贪心+二分

  • 写法1:手写二分
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Pos
{
	int x, t;
	bool operator < (const Pos &b) const
	{
		return x < b.x || x == b.x && t < b.t;
	}
} a[N];
int an, n, b[N], d[N], len, ans;//d[i]:b序列长为i的不降子序列的最小末尾元素 
int main()
{
	int x, t;
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> x >> t;
		if(t >= x)//只保留可能走到的点 
			a[++an] = Pos{x, t};
	}
	sort(a+1, a+1+an);
	for(int i = 1; i <= an; ++i)
		b[i] = a[i].t-a[i].x;
	d[++len] = b[1];
	for(int i = 2; i <= an; ++i)
	{
		if(b[i] >= d[len])
			d[++len] = b[i];
		else
		{
			int l = 1, r = len;
			while(l <= r)
			{
				int mid = (l+r)/2;
				if(d[mid] > b[i])
					r = mid-1;
				else
					l = mid+1;
			}
			d[l] = b[i];
		}
	}
	cout << len;
	return 0;
}
  • 写法2:使用STL upper_bound
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Pos
{
	int x, t;
	bool operator < (const Pos &b) const
	{
		return x < b.x || x == b.x && t < b.t;
	}
} a[N];
int an, n, b[N], d[N], len, ans;//d[i]:b序列长为i的不降子序列的最小末尾元素 
int main()
{
	int x, t;
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> x >> t;
		if(t >= x)//只保留可能走到的点 
			a[++an] = Pos{x, t};
	}
	sort(a+1, a+1+an);
	for(int i = 1; i <= an; ++i)
		b[i] = a[i].t-a[i].x;
	d[++len] = b[1];
	for(int i = 2; i <= an; ++i)
	{
		if(b[i] >= d[len])
			d[++len] = b[i];
		else
		{
			int l = upper_bound(d+1, d+1+len, b[i])-d;
			d[l] = b[i];
		}
	}
	cout << len;
	return 0;
}

解法3:离散化+树状数组

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Pos
{
	int x, t;
	bool operator < (const Pos &b) const
	{
		return x < b.x || x == b.x && t < b.t;
	}
} a[N];
vector<int> t;
int an, n, b[N], ans, tree[N];//c[x]:表示以b数组值为x的元素为结尾的最长上升子序列的长度。tree是c的树状数组,维护区间最大值。 
void discretization()
{
	for(int i = 1; i <= an; ++i)
		t.push_back(b[i]);
	sort(t.begin(), t.end());
	t.erase(unique(t.begin(), t.end()), t.end());
	for(int i = 1; i <= an; ++i)
		b[i] = upper_bound(t.begin(), t.end(), b[i])-t.begin();//保证值>=1,因为要作为树状数组的下标 
}
int lowbit(int x)
{
	return x & -x;
}
void update(int i, int v)//c[i] = v
{
	for(int x = i; x <= an; x += lowbit(x))
		tree[x] = max(tree[x], v);
}
int maxVal(int i)//max{c[1]...c[i]}
{
	int res = 0;
	for(int x = i; x > 0; x -= lowbit(x))
		res = max(res, tree[x]);
	return res;
}
int main()
{
	int x, t;
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> x >> t;
		if(t >= x)//只保留可能走到的点 
			a[++an] = Pos{x, t};
	}
	sort(a+1, a+1+an);
	for(int i = 1; i <= an; ++i)
		b[i] = a[i].t-a[i].x;
	discretization();
	for(int i = 1; i <= an; ++i)
	{
		int dpVal = maxVal(b[i])+1;
		ans = max(ans, dpVal);
		update(b[i], dpVal);
	}
	cout << ans;
	return 0;
}
相关推荐
Ricky_Theseus2 小时前
静态链接与动态链接
c++
摸个小yu2 小时前
【力扣LeetCode热题h100】链表、二叉树
算法·leetcode·链表
汀、人工智能2 小时前
[特殊字符] 第93课:太平洋大西洋水流问题
数据结构·算法·数据库架构·图论·bfs·太平洋大西洋水流问题
ZPC82102 小时前
rviz2 仿真控制器与真实机器人切换
人工智能·算法·机器人
澈2072 小时前
双指针,数组去重
c++·算法
小辉同志2 小时前
207. 课程表
c++·算法·力扣·图论
CheerWWW2 小时前
深入理解计算机系统——位运算、树状数组
笔记·学习·算法·计算机系统
feng_you_ying_li3 小时前
C++11,{}的初始化情况与左右值及其引用
开发语言·数据结构·c++
锅挤3 小时前
数据结构复习(第一章):绪论
数据结构·算法