JZOJ 3401 & JZOJ 5673. 【GDOI2018Day1模拟4.20】爬山法

38 篇文章 0 订阅
17 篇文章 0 订阅

Description

Description

Input

输入文件的第一行含有一个正整数 n,代表山的顶点数。
接下来 n 行,每行包含两个整数 x i 和 y i ,代表一个顶点的坐标。输入保证 x i单调递增。

Output

输出 n 行,第 i 行包含一个整数,代表从第 i 个顶点出发走到最高点需要经过多少段。

Sample Input

5
1 5
2 4
3 9
4 0
5 2

Sample Output

2
1
0
1
2

Data Constraint

Data Constraint

Hint

Hint

Solution

  • 我们设 l[i] (r[i]) 表示点 i 向左(右)看能看到的点最高是哪一个。

  • 这用单调栈可以 O(N) 求出来——左右扫一遍就可以了。

  • 之后我们发现最难统计的就是一个点不断发现新的高点,从而一直拐弯。

  • 那么我们又设 fl[i] (fr[i]) 表示点 i 向左(右)第一个能看到更高点的位置。

  • 这我们也可以近乎 O(N) 求出来,一样是扫一遍,用前面的 fl[] 更新当前的 fl[i]

  • 那么点 i 能看到的最高高度就是 f[i]=max(fl[i],fr[i]) ,走的方向也就确定了。

  • 之后我们以最高点为 root ,连边 (fl[i],i) (或 fr[i] ,以走的方向而定)。

  • 显然这是一个树结构,因为每个点始终会到最高点(一定连通),目标点又越来越高(没环)。

  • 于是跑一边 dfs ,就能算出每个点走的段数了。

  • 时间复杂度近乎 O(N)

Code

#include<cstdio>
#include<cctype>
using namespace std;
const int N=2e5+5;
struct data
{
    int x;
    long long y;
}a[N];
int top,tot,root;
int first[N],nex[N],en[N];
int l[N],r[N],st[N],ans[N],fl[N],fr[N],fx[N];
long long f[N];
double d[N];
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) X=(X<<1)+(X<<3)+(ch^48),ch=getchar();
    return w?-X:X;
}
inline void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline double get(int x,int y)
{
    return (a[x].y-a[y].y)*1.0/(a[x].x-a[y].x);
}
inline int abs(int x)
{
    return x<0?-x:x;
}
void dfs(int x)
{
    for(int i=first[x];i;i=nex[i])
    {
        ans[en[i]]=ans[x]+abs(x-en[i]);
        dfs(en[i]);
    }
}
inline void insert(int x,int y)
{
    nex[++tot]=first[x];
    first[x]=tot;
    en[tot]=y;
}
int main()
{
    int n=read();
    for(int i=1;i<=n;i++) a[i].x=read(),a[i].y=read();
    for(int i=1;i<=n;i++)
    {
        while(top>1 && d[top]<=get(i,st[top])) top--;
        if(a[st[top]].y>a[i].y) l[i]=st[top];
        st[++top]=i;
        if(top>1) d[top]=get(i,st[top-1]);
    }
    top=0;
    for(int i=n;i;i--)
    {
        while(top>1 && d[top]>=get(i,st[top])) top--;
        if(a[st[top]].y>=a[i].y) r[i]=st[top]; else r[i]=n+1;
        st[++top]=i;
        if(top>1) d[top]=get(i,st[top-1]);
    }
    r[n]=n+1;
    for(int i=1;i<=n;i++) a[i].y=a[i].y*1000000+i;
    for(int i=1;i<=n;i++)
    {
        long long h=a[i].y;
        if(a[r[i]].y>h) h=a[r[i]].y,fx[i]=2;
        if(a[l[i]].y>h) h=a[l[i]].y,fx[i]=1;
        f[i]=h;
    }
    for(int i=1;i<=n;i++)
    {
        int j=i-1;
        while(j && f[i]>=f[j]) j=fl[j];
        fl[i]=j?j:l[i];
    }
    for(int i=n;i;i--)
    {
        int j=i+1;
        while(j<=n && f[i]>=f[j]) j=fr[j];
        fr[i]=j<=n?j:r[i];
    }
    for(int i=1;i<=n;i++)
        if(!fx[i]) root=i; else
            if(fx[i]==1) insert(fl[i],i); else insert(fr[i],i);
    dfs(root);
    for(int i=1;i<=n;i++) write(ans[i]),putchar('\n');
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值