【JZOJ 3401】Pty爬山

Description

坐标系里有N个点,保证他们的x坐标递增,它们互相连接形成一些连续的线段。每个点会向自己的位置能看到的最高点(能看到指视线不经过其它线段)沿着线段移动。若移动过程中到达的点能看到的最高点比原先的最高点高,则向新的最高点移动……若该点就是最高点则停止移动。求从每个点出发移动的步数。

Analysis

题目没有说明,但是数据是三点共线是可以被看见的(这证明了地球不是圆的2333)。
这道题我花了整场比赛去搞,结果比赛时只有10分QAQ.
首先预处理出每个点能够看到的最高点。正反各搜一遍,比较一下斜率就好了。 O(n) 解决。
然后一个直观的想法就是模拟走路的过程。但是这样最坏情况可以达到 O(n3) ,于是爆炸。
显然地,无需如此暴力。
我们可以用一棵线段树,记录每个点能看到的最高点的高度。这样走的时候直接线段树查询,复杂度变成 O(n2log2n) ,这样就能拿到60分了。
实际上,我们可以发现,这道题的图的关系还是比较显著的。我们只需让每个点能看到的最高点向该点连边。然后从全图最高点开始bfs一遍记录边权更新每个点的答案就好了。 O(nlog2n) ,于是乎就能通过所有数据了。

Code

#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
using namespace std;
typedef double db;
const int N=200010,M=N*2;
int n,pos,b[N],b1[N],b2[N],mx[N*4];
int tot,to[M],wei[M],next[M],last[N],dis[N];
queue<int> q;
struct point
{
    int x,y;
}a[N];
db slope(point a,point b){return (a.y-b.y)*1.0/(a.x-b.x);}
void link(int u,int v,int w)
{
    to[++tot]=v,wei[tot]=w,next[tot]=last[u],last[u]=tot;
}
void pre()
{
    scanf("%d",&n);
    fo(i,1,n) scanf("%d %d",&a[i].x,&a[i].y);
    fd(i,n-1,1) b1[i]=i+1;
    b1[n]=n;
    fd(i,n-2,1)
        for(;slope(a[i],a[b1[b1[i]]])>=slope(a[i],a[b1[i]]);b1[i]=b1[b1[i]])
            if(b1[i]==n) break;
    fo(i,2,n) b2[i]=i-1;
    b2[1]=1;
    fo(i,3,n)
        for(;slope(a[i],a[b2[b2[i]]])<=slope(a[i],a[b2[i]]);b2[i]=b2[b2[i]])
            if(b2[i]==1) break;
    fo(i,1,n)
    {
        b[i]=i;
        if(a[b1[i]].y>=a[b[i]].y) b[i]=b1[i];
        if(a[b2[i]].y>a[b[i]].y) b[i]=b2[i];
    }
}
void build(int v,int l,int r)
{
    if(l==r)
    {
        mx[v]=a[b[l]].y;
        return;
    }
    int mid=(l+r)>>1;
    build(v*2,l,mid);
    build(v*2+1,mid+1,r);
    mx[v]=max(mx[v*2],mx[v*2+1]);
}
void findl(int v,int l,int r,int z)
{
    if(mx[v]<z) return;
    if(l==r)
    {
        pos=min(pos,l);
        return;
    }
    int mid=(l+r)>>1;
    if(mx[v*2]>=z) findl(v*2,l,mid,z);
    else
    if(mx[v*2+1]>=z) findl(v*2+1,mid+1,r,z);
}
void findr(int v,int l,int r,int z)
{
    if(mx[v]<z) return;
    if(l==r)
    {
        pos=max(pos,r);
        return;
    }
    int mid=(l+r)>>1;
    if(mx[v*2+1]>=z) findr(v*2+1,mid+1,r,z);
    else
    if(mx[v*2]>=z) findr(v*2,l,mid,z);
}
void find(int v,int l,int r,int x,int y,int z,bool p)
{
    if(l==x && r==y)
    {
        if(p) findr(v,x,y,z);
        else findl(v,x,y,z);
        return;
    }
    int mid=(l+r)>>1;
    if(y<=mid) find(v*2,l,mid,x,y,z,p);
    else
    if(x>mid) find(v*2+1,mid+1,r,x,y,z,p);
    else find(v*2,l,mid,x,mid,z,p),
    find(v*2+1,mid+1,r,mid+1,y,z,p);
}
int val(int x,int y)
{
    pos=y;
    if(x<y)
    {
        x++;
        find(1,1,n,x,y,a[y].y+1,0);
        return pos;
    }
    x--;
    find(1,1,n,y,x,a[y].y,1);
    return pos;
}
void bfs(int S)
{
    q.push(S);
    dis[S]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=last[u];i;i=next[i])
        {
            int v=to[i];
            dis[v]=dis[u]+wei[i];
            q.push(v);
        }
    }
}
int main()
{
    pre();
    build(1,1,n);
    int root=1;
    fo(i,1,n)
    {
        if(i==b[i]) {root=i;continue;}
        int j=val(i,b[i]);
        link(j,i,abs(i-j));
    }
    bfs(root);
    fo(i,1,n) printf("%d\n",dis[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值