【GDOI2014模拟】​Pty爬山

Description

平面上有n个点,我们把ai和ai+1连上边(保证x坐标递增),从每个点出发,我们会朝着我们当前能看到的最高点走去。如果在某个点,看到了比当前还要高的点,那我们会向这个点走去。(高度相同,我们规定右边的高)
求从每个点出发,走到最高点的步数。
n<=2*10^5

Solution

首先,我们需要求出,每个点所能看到的最高点。
分为左侧和右侧两种情况讨论。
我们设f[i]表示i往左看所能看到的最高点。若比i矮则f[i]=0.
那么,我们每次往左找,判断一下i能不能看到f[i-1],如果可以就判断f[f[i-1]]。
右边类似,注意高度相同的。
那么我们就有了f和g数组。
再考虑走法。如果我们从A出发,在B点看到了比A还高的点,那么我们就相当于是从A走到了B,再从B出发。就把A指向B,最后做一遍bfs就可以了。
如何处理B?
设H[i]表示i所能看到的最高点,那么H[B]>=H[A]。
那么我们相当于求每个点左/右边第一个大于等于他的点。
这个可以用双向链表/线段树等各种东西求。
你喜欢就好O(∩_∩)O~

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 200005
using namespace std;
typedef double db;
struct note{int x,y;}a[N];
struct node{int v,w,x;}h[N];
bool cmp(node x,node y) {
    return x.v<y.v||x.v==y.v&&x.x<y.x;
}
int f[N],g[N],l[N],r[N],d[N],ans[N],n,tot;
int last[N],next[N],v[N],t[N];
void add(int x,int y,int z) {
    t[++tot]=y;v[tot]=z;next[tot]=last[x];last[x]=tot;
}
db cross(int i,int j) {
    return (a[i].y-a[j].y)*1.0/(a[i].x-a[j].x);
}
int main() {
    scanf("%d",&n);
    fo(i,1,n) scanf("%d%d",&a[i].x,&a[i].y),
    l[i]=i-1,r[i]=i+1;
    fo(i,2,n) {
        int k=i-1;
        while (f[k]) 
            if (cross(f[k],k)<=cross(k,i)) k=f[k];else break;
        if (a[k].y>a[i].y) f[i]=k;
    }
    fd(i,n-1,1) {
        int k=i+1;
        while (g[k]) 
            if (cross(g[k],k)>=cross(k,i)) k=g[k];else break;
        if (a[k].y>=a[i].y) g[i]=k;
    }
    fo(i,1,n) {
        h[i].w=i;
        if (a[f[i]].y>a[g[i]].y) h[i].v=a[f[i]].y,h[i].x=f[i];
        else h[i].v=a[g[i]].y,h[i].x=g[i];
    }
    sort(h+1,h+n+1,cmp);
    fo(i,1,n) {
        int x=h[i].w;
        if (!f[x]&&!g[x]) {d[1]=x;continue;}
        if (a[f[x]].y>a[g[x]].y) add(l[x],x,x-l[x]);
        else add(r[x],x,r[x]-x);
        l[r[x]]=l[x];r[l[x]]=r[x];
    }
    int i=0,j=1;
    while (i<j) rep(k,d[++i]) if (!ans[t[k]]) {
        ans[t[k]]=ans[d[i]]+v[k];
        d[++j]=t[k];
    }
    fo(i,1,n) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值