开车旅行-st表

P1081 开车旅行-st表

题意

题目好长,有 \(A\) 走改点向后的次短边, \(B\) 向后走最短边,路径长度定义为两个城市的高度差,如果到两个城市距离相同,高度低的城市先为短边。第一问:从哪开始能在总长限制 \(x\) 下让 \(A\) 的距离 \(/\) \(B\) 走的距离最大。 第二问:给定 \(m\) 个起点和距离限制,问 \(A\)\(B\) 分别走的距离。

思路

写题 $ing$

现在再写第三题,第三题的思路是

  1. 每个点对应的下一个点找出来(最短和次短),
  2. \(3\)\(st\) 表,分别是以 \(a\) 为起点的 \(nex\) ,再存对应的 \(a/b\) 的经过和

先在第一个要求就写不出来,暴力找要 \(n^2\) ,原本想着 \(sort\) 一下,但是还有一个要求:下标还要更大。这让我想到了二维篇序优化,但是不大一样。

又想到了之前有个不断排序会 \(T\) ,但是可以自己维护,每次多一个数 \(logn\)

我想到了,用 \(set\) ,从后向前找.

很好,现在边处理出来了,想想 \(st\) 表怎么建。 \(dfs\) 回来的时候每个 \(dfsA\) 的地方建,顺便把这个点标起来,下次从没标的下去,预算也是 \(O(nlogn)\),不对。不用 \(dfs\),我们已经知道了每个点 \(A/b\) 的时候走的是那条边。也就是说图可能都不用建,没事建都建了,但是有以个同样的问题,并不是所有起点都是 \(1\)\(stb\) 预处理不能只在 \(1\) 上。想错了,每个点都要建 \(stb\) 的来这。

现在写 \(wa\) 了,总结一下 \(wa\) 的可能原因:

  1. 预处理边的时候我采用的是 \(set\) 快速找出这四个可能的点,但是方法比较笨,是和最小的值进行比较,然后留最小的和次小的值,这样可能有一个问题就是。。。好吧似乎不会有问题
  2. 题目有要求说点不能走的时候,即 \(B=0\) 的时候,视为无穷大,两个无穷大之间同样找高度最高的起点点。

现在调出来了,出现了一个未解之谜,我的 \(set\) 前后找 \(3\) 位才是正确的。。


正文:由于对于每个点来说 \(A/B\) 选取的边是固定且有规律的,考虑 \(st\) 表优化,发现如果暴力建边直接就 \(T\) 了,所以用 \(set\) 维护值的大小。时间复杂度 \(O(nlogn)\).

发现我还没说过怎么用 \(st\) 表,发现起点都是从 \(A\) 开始的, \(A\) 的下一步边是 \(nex[i][0]\)\(suma[i][0]\) 就是这一步的距离,在这一步之后是 \(B\) 走到 \(nex[i][1]\),走的距离可以另外记录下来,就是 \(sumb[i][1]\),此后直接二进制向后跳就行了,具体为什么这样是正确的画一下就知道了,主要还是因为后面都是 \(2\) 的幂,一定包含了处理出来的 \(2\) 步。

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e5+10;
constexpr int maxl = 20;
constexpr int INF = 0x3f3f3f3f3f3f3f3f;
constexpr double eps=1e-6;

typedef struct high
{
    int h,id;

    high(int x,int y):h(x),id(y) {}
    high():h(INF),id(0) {}

    bool operator<(const high &t) const
    {
        return h<t.h;
    }
} high;

typedef struct point
{
    int l,lw;
    int r,rw;
} point;

int n;
int hi[maxn];
point pi[maxn];
set<high> h_set;

int logn[maxn];
int nex[maxn][maxl];// 下一个点
int suma[maxn][maxl];// A 的距离
int sumb[maxn][maxl];// B 走的距离

void init()
{
    for(int i=2; i<maxn-5; ++i)
    {
        logn[i]=logn[i/2]+1;
    }
}

bool cmp(high &x,high &y)// 此处 h 存的是高度差
{
    return x.h!=y.h ? x.h<y.h : hi[x.id]<hi[y.id];
}

int A,B;

void get_sum(int s,int x)// 起点和距离
{
    A=B=0;

    for(int i=18; i>=0; --i)// 跳st表
    {
        if(nex[s][i]==0 || suma[s][i]+sumb[s][i]>x)
        {
            continue;
        }
        x-=suma[s][i]+sumb[s][i];
        A+=suma[s][i];
        B+=sumb[s][i];
        s=nex[s][i];
    }
}

signed main()
{
#ifndef ONLINE_JUDGE
    freopen("drive.in","r",stdin);
    freopen("drive.out","w",stdout);
#endif // ONLINE_JUDGE

    init();
    scanf("%lld",&n);
    for(int i=1; i<=n; ++i)
    {
        scanf("%lld",&hi[i]);
    }
    h_set.emplace(high(-INF,0));// 哨兵,少两个这份代码应该没有关系
    h_set.emplace(high(-INF+1,0));
    h_set.emplace(high(INF,0));
    h_set.emplace(high(INF-1,0));

    for(int i=n; i>=1; --i)
    {
        high tmp[9];
        auto id=h_set.lower_bound(high(hi[i],i));
        auto it=id;
        int cnt=0;
        for(; it!=h_set.begin() && cnt<3; --it,++cnt)// 前后取6个,莫名其妙??
        {
            tmp[cnt]= {abs(it->h-hi[i]),it->id};
        }
        it=++id;
        for(; it!=h_set.end() && cnt<6; ++it,++cnt)
        {
            tmp[cnt]= {abs(it->h-hi[i]),it->id};
        }

        sort(tmp,tmp+cnt,cmp);

        pi[i].l = tmp[0].id;// 存边(其实只用记录B就行了)
        pi[i].lw = tmp[0].h;
        pi[i].r = tmp[1].id;
        pi[i].rw = tmp[1].h;
        h_set.emplace(high(hi[i],i));// 差进去

        nex[i][0]=tmp[1].id;// st表第一步
        suma[i][0]=tmp[1].h;
    }

    for(int i=n; i>=1; --i)// 后往前
    {
        nex[i][1]=pi[nex[i][0]].l;// B 走一步
        sumb[i][1]=pi[nex[i][0]].lw;
        suma[i][1]=suma[i][0];
        for(int j=2; j<=18; ++j)
        {
            nex[i][j]=nex[nex[i][j-1]][j-1];
            if(nex[i][j]==0)
            {
                break;
            }
            suma[i][j]=suma[i][j-1]+suma[nex[i][j-1]][j-1];
            sumb[i][j]=sumb[i][j-1]+sumb[nex[i][j-1]][j-1];
        }
    }

    int s,x,m;// 求答案
    scanf("%lld",&x);
    int mi=0;
    double mis=INF;
    for(int i=1; i<=n; ++i)
    {
        get_sum(i,x);
        if(B==0)
        {
            if(fabs(mis-INF)<=eps)
            {
                if(hi[i]>hi[mi])
                {
                    mi=i;
                }
            }
            continue;
        }
        if(fabs(1.0*A/B-mis)<=eps)
        {
            if(hi[i]>hi[mi])
            {
                mi=i;
            }
        }
        else if(1.0*A/B<mis)
        {
            mis=1.0*A/B;
            mi=i;
        }
    }
    printf("%lld\n",mi);

    scanf("%lld",&m);
    for(int i=1; i<=m; ++i)
    {
        scanf("%lld%lld",&s,&x);
        get_sum(s,x);
        printf("%lld %lld\n",A,B);
    }

    return 0;
}
posted @ 2025-11-15 07:35  玖玮  阅读(10)  评论(0)    收藏  举报