洛谷P7599雨林跳跃

Posted stoorz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷P7599雨林跳跃相关的知识,希望对你有一定的参考价值。

题目

题目链接:https://www.luogu.com.cn/problem/P7599
在苏门答腊岛的热带雨林中,有 \\(N\\) 棵树排成一排,从左到右依次用 \\(0\\)\\(N-1\\) 进行编号,其中 \\(i\\) 号树的高度为 \\(H[i]\\),且所有树的高度互不相同
Pak Dengklek 正在训练一只猩猩,让她能够从一棵树上跳到另一棵树上。对于一次跳跃,猩猩可以从一棵树,向左或向右跳到比当前这棵树高的第一棵树上。形式化地,如果猩猩当前在 \\(x\\) 号树,那么当且仅当满足下列条件之一时,她能够跳到 \\(y\\) 号树上:

  • \\(y\\) 是满足 \\(H[z]>H[x]\\) 的所有 \\(z\\) 中比 \\(x\\) 小的最大非负整数;或者:
  • \\(y\\) 是满足 \\(H[z]>H[x]\\) 的所有 \\(z\\) 中比 \\(x\\) 大的最小非负整数。

Pak Dengklek 有 \\(Q\\) 个跳跃计划,每个计划用四个整数 \\(A\\)\\(B\\)\\(C\\)\\(D\\)\\(A \\le B<C \\le D\\))来描述。对于每个计划,Pak Dengklek 想知道猩猩是否能够从某棵树 \\(s\\)\\(A \\le s \\le B\\))出发,经过若干次跳跃,到达某棵树 \\(e\\)\\(C \\le e \\le D\\))。若该计划可行,Pak Dengklek 还想知道可行方案中猩猩需要的最少跳跃次数。
你需要实现下列函数:

void init(int N, int[] H)

  • \\(N\\):树的数量。
  • \\(H\\):大小为 \\(N\\) 的数组,\\(H[i]\\) 表示 \\(i\\) 号树的高度。
  • 该函数在第一次 minimum_jumps 的调用前,将会被调用恰好一次。

int minimum_jumps(int A, int B, int C, int D)

  • \\(A,B\\):可以用作起点的树的编号范围。
  • \\(C,D\\):可以用作终点的树的编号范围。
  • 该函数需要返回可行方案中猩猩需要的最少跳跃次数,或者返回 \\(-1\\) 表示该计划不可行。
  • 该函数将被调用恰好 \\(Q\\) 次。

\\(n\\leq 2\\times 10^5;Q\\leq 10^5\\)

思路

首先考虑 \\(A=B,C=D\\) 时怎么做。
不难发现可以从 \\(A\\)\\(C\\) 的充要条件是 \\([A,C)\\) 中最高的树都没有 \\(C\\) 高。
最优方案一定是先不断往左右两边更高的跳,直到下一次跳的高度超过 \\(C\\) 的高度。然后不断往右跳直到 \\(C\\) 为止。
所以我们预处理 \\(f[i][j],g[i][j],h[i][j]\\) 分别表示从 \\(i\\) 开始跳 \\(2^j\\) 次,其中每次往左右更高的跳 / 往右跳 / 往左跳能跳到的位置;再维护一个 ST 表用于求区间最大值,然后就可以单次 \\(O(\\log n)\\) 查询了。

接下来考虑 \\(C=D\\) 时怎么做。
我们找到 \\(C\\) 左边第一个高度大于 \\(C\\) 的位置 \\(k\\)。如果 $k\\geq $ 则无解,否则从 \\([\\max(k+1,A),B]\\) 开始跳一定最优。因为其他点可以跳到的,这个位置一定也可以通过不会更多的次数跳到。

最后考虑 \\(100\\%\\) 的数据,也就是 \\(A\\leq B<C\\leq D\\)
依然找到 \\([B,C)\\) 中最高点 \\(p\\),然后再找 \\([C,D]\\) 中第一个高于 \\(p\\) 的位置 \\(x\\);再找向左第一个高于 \\(x\\) 的位置 \\(q\\),再找到 \\([C,D]\\) 中一个高于 \\(q\\) 的位置 \\(y\\)
可以证明,最优解最后到达的一定是 \\(x\\)\\(y\\)

  • 如果 \\(q<A\\),那么任意 \\([A,B]\\) 中的点去到 \\([C,D]\\) 的路径上一定至少会经过 \\(p\\)\\(q\\) 中的一个。如果经过的是 \\(p\\),下一步直接跳到 \\(x\\),否则下一步直接跳到 \\(y\\)
  • 如果 \\(A\\leq q\\leq B\\),那么最优解就是从 \\(q\\) 直接跳到 \\(y\\)
  • 如果 \\(q>B\\),那么 \\([B,C)\\) 中的最高点应该是 \\(q\\) 而非 \\(p\\),矛盾。

所以这样可能的终点就只有 \\(2\\) 个,用 \\(C=D\\) 的方法分别求出来后取较小值即可。
时间复杂度 \\(O(n\\log n)\\)

代码

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

const int N=200010,LG=18;
int n,Q,a[N],f[N][LG+1],g[N][LG+1],h[N][LG+1],mx[N][LG+1],lg[N];
stack<int> st;

void init(int N, vector<int> H)
{
	n=N;
	for (int i=1;i<=n;i++)
		a[i]=H[i-1],mx[i][0]=i;
	for (int i=1;i<=n;i++)
	{
		while (st.size() && a[st.top()]<a[i]) st.pop();
		if (st.size()) h[i][0]=st.top();
		st.push(i);
	}
	while (st.size()) st.pop();
	for (int i=n;i>=1;i--)
	{
		while (st.size() && a[st.top()]<a[i]) st.pop();
		if (st.size()) g[i][0]=st.top();
		st.push(i);
	}
	for (int i=1;i<=n;i++)
	{
		f[i][0]=(a[h[i][0]]>a[g[i][0]]) ? h[i][0] : g[i][0];
		if (i>1) lg[i]=lg[i>>1]+1;
	}
	for (int j=1;j<=LG;j++)
		for (int i=1;i<=n;i++)
		{
			f[i][j]=f[f[i][j-1]][j-1];
			g[i][j]=g[g[i][j-1]][j-1];
			h[i][j]=h[h[i][j-1]][j-1];
			if (i+(1<<j)-1<=n)
				mx[i][j]=(a[mx[i][j-1]]>a[mx[i+(1<<j-1)][j-1]]) ? mx[i][j-1] : mx[i+(1<<j-1)][j-1];
		}
}

int findmax(int l,int r)
{
	int k=lg[r-l+1];
	return (a[mx[l][k]]>a[mx[r-(1<<k)+1][k]]) ? mx[l][k] : mx[r-(1<<k)+1][k];
}

int binary(int l,int r,int x)
{
	if (r<=x)
	{
		for (int i=LG;i>=0;i--)
			if (h[x][i] && h[x][i]>r) x=h[x][i];
		if (h[x][0]>=l) return h[x][0];
	}
	else
	{
		for (int i=LG;i>=0;i--)
			if (g[x][i] && g[x][i]<l) x=g[x][i];
		if (g[x][0]<=r) return g[x][0];
	}
	return 0;
}

int query(int l,int r,int k)
{
	if (!k) return -1;
	int p=h[k][0];
	if (p>r) return -1;
	int q=findmax(max(p+1,l),r),ans=0;
	for (int i=LG;i>=0;i--)
		if (f[q][i] && a[f[q][i]]<=a[k])
			q=f[q][i],ans+=(1<<i);
	for (int i=LG;i>=0;i--)
		if (g[q][i] && a[g[q][i]]<=a[k])
			q=g[q][i],ans+=(1<<i);
	return ans;
}

int minimum_jumps(int A, int B, int C, int D)
{
	A++; B++; C++; D++;
	int p=findmax(B,C-1),x=binary(C,D,p);
	int q=binary(1,B,x),y=binary(C,D,q);
	int c1=query(A,B,x),c2=query(A,B,y);
	if (c1==-1) return c2;
	if (c2==-1) return c1;
	return min(c1,c2);
}
/*
int main()
{
	int N, Q;
	assert(2 == scanf("%d %d", &N, &Q));
	std::vector<int> H(N);
	for (int i = 0; i < N; ++i)
		assert(1 == scanf("%d", &H[i]));
	init(N, H);
	for (int i = 0; i < Q; ++i)
	{
		int A, B, C, D;
		assert(4 == scanf("%d %d %d %d", &A, &B, &C, &D));
		printf("%d\\n", minimum_jumps(A, B, C, D));
	}
	return 0;
}
*/

以上是关于洛谷P7599雨林跳跃的主要内容,如果未能解决你的问题,请参考以下文章

洛谷 P1052 过河

小a和uim之大逃离(洛谷 1373)

洛谷 P1301 魔鬼之城

洛谷 P1373 小a和uim之大逃离

洛谷 P1373 小a和uim之大逃离

洛谷P1052 过河(状压dp)