​Pty爬山

题目描述

在Pty学校附近,有一座名之为岳之麓的高山。Pty很喜欢和(哔——)一起爬山。

山的平面模型如下:

山由一个顶点集:A1,A2…An给定,保证Ai的x单调递增。我们将Ai和Ai+1之间连上线段,表示山的某一段。如下图所示:

这里写图片描述

​Pty想要爬到这座山的最高的顶点,当两个顶点的高度相同时,我们认为x比较大的顶点要高一些。Pty不是盲人,所以他将会在爬山时采取一些策略,使得他能够尽量快的到达最高的顶点。

Pty从初始的顶点出发,往左右看去,他将朝他能够看到的最高的顶点方向走去。当走到每一个顶点时,他都会重新观察,如果这时看到的顶点比之前看到的顶点还要高,那么他将选择此时看到的顶点走去,直到他到达最高点为止。

例如上图中:Pty从A4点出发。他能够看到的最高点是A6,所以他将会向右侧走去。当他到达A5号点时,能够看到A1点比A6点更高,所以他会调转方向,向左侧走去。由于A1是最高的顶点,所以他将一直往左侧走,直到到达A1为止。

Pty想知道从每一个顶点出发,分别需要走过多少段才能到达最高点。例如上图中从A4出发需要走过5段才能到达最高点。

单调栈或一种机智的方法(我也不知道有没有名字)

如何处理每个点看到的最高点呢?
我们分两个方向考虑。
(我用了机智的方法,单调栈其实也类似)
设fa1[i]表示i在i的左侧能看到的最高点
设j=i-1,若i能看到fa1[j]则fa1[i],j都更新为fa1[j],否则i++
我们需要证明的是若i看不到fa1[j],那么fa1[j]~j的点i都看不到,然而这显然(不清楚就画个图看看)
所以可以O(n)处理出fa1,设fa2[i]表示i在i的右侧能看到的最高点,同样类似的处理。
设f[i]表示i能看到的最高点的高度,可以通过fa1[i],fa2[i]比较得出,
设p[i]看到的最高点的方向(1表示在i左侧,2表示在右侧)。
现在我们已经得出了每个点应该前进的方向,怎么求出从每一个点出发所需要经过的段数?假设我们现在从A号点出发,当它走到B号点时,看到了一座比A号点看到的更高的顶点,此时是不是就是相当于从B号点出发了呢?答案是肯定的。
所以我们让A指向B,可以发现最后的结构图将是一棵树,dfs一遍就能得出ans。
如何快速的找到B?
设f1[i]表示i在i的左侧看到的离i最近的满足f值比i大的点。
设j=i-1,若f[i]>f[j]则j=1[j],否则f1[i]=j
正确性同样显然。
设f2[i]表示i在i的右侧看到的离i最近的满足f值比i大的点。
类似做法可得f2。
最后根据p[i],f1[i],f2[i]来连边。dfs得出ans。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=200000+5;
int f1[maxn],f2[maxn],x[maxn],y[maxn],fa1[maxn],fa2[maxn],n,k[maxn],next[maxn],g[maxn],p[maxn],root,num;
ll ans[maxn],yy[maxn],f[maxn];
void add(int x,int y){
    next[++num]=k[x];
    k[x]=num;g[num]=y;
}
void read(int &n){
    char ch=getchar();
    while(ch!='-'&&(ch<'0')||(ch>'9'))ch=getchar();n=0;
    int q=1;if (ch=='-') ch=getchar(),q=-1;
    while(ch>='0'&&ch<='9')n=n*10+ch-'0',ch=getchar();n=n*q;
}
void dfs(int x){
    int i=k[x];
    while (i>0){
        ans[g[i]]=ans[x]+abs(x-g[i]);dfs(g[i]);
        i=next[i];
    }
} 
int main(){
    read(n);
    for (int i=1;i<=n;i++) read(x[i]),read(y[i]),yy[i]=(ll)y[i]*1000000+i;y[0]=y[n+1]=0;
    for (int i=1;i<=n;i++){
        int k=i-1,j=fa1[k];fa1[i]=k;bool bz=1;
        ll k1=(y[k]-y[i]),k2=x[i]-x[k];
        while (j>0&&bz) {
            ll k3=y[j]-y[i],k4=x[i]-x[j];
            if (k1*k4<=k3*k2) fa1[i]=j,j=fa1[j],k1=k3,k2=k4;else bz=0;
        }if (y[fa1[i]]<y[i]) fa1[i]=0;
    }fa2[n+1]=n+1;
    for (int i=n;i>0;i--){
        int k=i+1,j=fa2[k];fa2[i]=k;bool bz=1;
        ll k1=(y[k]-y[i]),k2=x[k]-x[i];
        while (j<=n&&bz){
            ll k3=y[j]-y[i],k4=x[j]-x[i];
            if (k1*k4<=k3*k2) fa2[i]=j,j=fa2[j],k1=k3,k2=k4;else bz=0;
        }if (y[fa2[i]]<y[i]) fa2[i]=n+1;
    }
    for (int i=1;i<=n;i++){
        int a=fa1[i],b=fa2[i];ll h=yy[i]; 
        if (yy[a]>h) h=yy[a],p[i]=1;
        if (yy[b]>h) h=yy[b],p[i]=2;f[i]=h;
    }
    for (int i=1;i<=n;i++){
        int j=i-1;
        while (j>0&&f[j]<=f[i]) j=f1[j];f1[i]=j;
        if (f1[i]==0) f1[i]=fa1[i];     
    }
    for (int i=n;i>0;i--){
        int j=i+1;
        while (j<=n&&f[j]<=f[i]) j=f2[j];f2[i]=j; 
        if (f2[i]==n+1) f2[i]=fa2[i]; 
    }
    for (int i=1;i<=n;i++){
        if (p[i]==0) root=i;else
        if (p[i]==1) add(f1[i],i);else add(f2[i],i);
    }dfs(root);
    for (int i=1;i<=n;i++) printf("%lld\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值