• P7599-[APIO2021]雨林跳跃【二分,倍增,ST表】


    正题

    题目链接:https://www.luogu.com.cn/problem/P7599


    题目大意

    (n)棵树,在某棵树上时可以选择向左右两边第一棵比它高的树跳,现在(q)次询问从([A,B])中某个点出发跳到([C,D])中某个点的最少次数。

    (1leq nleq 2 imes 10^5)


    解题思路

    考虑到主要的阈值([B+1,C-1])中的最大值,一旦超过了这个值就只需要考虑是否大于([C,D])中的最大值就好了。

    那么我们考虑如何选取起点,首先我们显然不能超过([C,D])中的最大值(mx),在这一情况下我们需要找到一个最大的位置小于(mx)且在它后面没有比(mx)大的数(否则就跳不过去了),这个过程我们用二分加(ST)表可以实现。

    然后考虑选取起点如何跳跃,首先如果([B+1,C-1])中的最大值(lim)远大于起点的话我们肯定是优先考虑跳左右两边高的那个点,这样肯定是最优的。所以我们在不大于(lim)的情况下肯定是选取这种跳法。对于这样的跳跃我们处理出一棵树然后在上面倍增即可。

    然后当跳到最后一个小于(lim)的值时我们有两种方法,一种是继续这样跳此时我们的值大于(lim)了可以直接跳过([B+1,C-1])但是需要判断这个值是否小于(mx)。(否则就顺便跳过了([C,D]))。

    第二种方法是直接一直往右边跳直到到([C,D])段,这个我们的处理方法可以每个点向它右边比它大的第一个点连边,然后(lim)肯定在起点到根的路径上,直接用深度减去即可。

    记得判无解

    时间复杂度(O(nlog n))


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int N=2e5+10,T=18;
    int n,q,h[N],p[N],l[N],r[N],lg[N];
    int f[N][T],g[N][T],dep[N];
    int RMQ(int l,int r){
    	if(l>r)return 0;int z=lg[r-l+1];
    	return max(f[l][z],f[r-(1<<z)+1][z]);
    }
    void init(int N,std::vector<int> H) {
    	n=N;h[0]=n+1;
    	for(int i=1;i<=n;i++)
    		h[i]=H[i-1],p[h[i]]=i,f[i][0]=h[i];
    	for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
    	for(int j=1;(1<<j)<=n;j++)
    		for(int i=1;i+(1<<j)-1<=n;i++)
    			f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
    	for(int i=1;i<=n;i++)l[i]=i-1,r[i]=i+1;
    	for(int i=1;i<=n;i++){
    		int x=p[i];
    		r[l[x]]=r[x];l[r[x]]=l[x];
    		g[x][0]=(h[l[x]]>h[r[x]])?l[x]:r[x];
    	}
    	for(int j=1;j<T;j++)
    		for(int i=1;i<=n;i++)
    			g[i][j]=g[g[i][j-1]][j-1];
    	for(int i=n;i>=1;i--)dep[i]=dep[r[i]]+1;
    }
    int minimum_jumps(int A,int B,int C,int D){
    	A++;B++;C++;D++;int l=A,r=B;
    	int lim=RMQ(B+1,C-1),zc=RMQ(C,D);
    	if(RMQ(B,C-1)>zc)return -1;
    	while(l<=r){
    		int mid=(l+r)>>1;
    		if(RMQ(mid,B)>zc)l=mid+1;
    		else r=mid-1;
    	}
    	int x=p[RMQ(l,B)],ans=0;
    	if(h[x]>lim)return 1;
    	for(int i=T-1;i>=0;i--)
    		if(h[g[x][i]]<lim)x=g[x][i],ans+=(1<<i);
    	if(g[x][0]&&h[g[x][0]]<zc&&h[x]<lim)ans+=2;
    	else ans+=dep[x]-dep[p[lim]]+1;
    	return ans;
    }
    //vector<int> H;
    //int main()
    //{
    //	scanf("%d%d",&n,&q);
    //	for(int i=0,x;i<n;i++)
    //		scanf("%d",&x),H.push_back(x);
    //	init(n,H);
    //	while(q--){
    //		int A,B,C,D;
    //		scanf("%d%d%d%d",&A,&B,&C,&D);
    //		printf("%d
    ",minimum_jumps(A,B,C,D));
    //	}
    //	return 0;
    //}
    
  • 相关阅读:
    java设计模式0--设计模式简介
    Eclipse常用快捷键与代码模板
    hadoop文件系统与I/O流
    java学习笔记16--I/O流和文件
    hadoop压缩框架
    hadoop中典型Writable类详解
    java学习笔记15--多线程编程基础2
    redis配置密码的方法
    编写的windows程序,崩溃时产生crash dump文件的办法
    windows程序崩溃生成dump文件
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/15025811.html
Copyright © 2020-2023  润新知