CF515E Drazil and Park【思维+线段树】

这篇博客讨论了一只猴子在环形公园晨跑时如何选择路径以最大化能量消耗的问题。文章介绍了问题背景,包括公园树木的距离和高度,以及儿童玩耍的区域对猴子路线的影响。提出了将环形结构转化为链状结构的思路,并利用线段树或ST表维护最值,从而找到最优的两棵树。代码展示了如何实现这一算法,通过计算最大能量损耗来确定最佳晨跑路径。
摘要由CSDN通过智能技术生成

题意

有一只猴子,他生活在一个环形的公园里。有 n n n 棵树围绕着公园。第 i i i棵树和第 i + 1 i+1 i+1棵树之间的距离是 d i d_i di ,而第 n n n棵树和第一棵树之间的距离是 d n d_n dn 。第 i i i棵树的高度是 h i h_i hi

这只猴子每天要进行晨跑。晨跑的步骤如下:

· 他先选择两棵树;

· 然后爬上第一棵树;

· 再从第一棵树上下来,接着围绕着公园跑(有两个可能的方向)到第二棵树,然后爬上第二棵树;

· 最后从第二棵树上下来。

但是有一些小孩会在连续的一些树上玩耍。所以猴子不能经过这些树。

比如现在猴子选择的第 x x x棵和第 y y y棵树,那么该早晨他消耗的能量是 2 ( h x + h y ) + d i s t ( x , y ) 2(hx+hy)+dist(x,y) 2(hx+hy)+dist(x,y) 。由于某一条路径是被小孩子占据的,所以他只能跑另外一条,因此 d i s t ( x , y ) dist(x,y) dist(x,y) 是确定的。

现在给出第i天,孩子们会在第 a i a_i ai 棵树和 b i b_i bi 棵树之间玩耍。具体的,如果 a i ≤ b i ai≤bi aibi ,那么孩子玩耍的区间就是 [ a i , b i ] [ai,bi] [ai,bi] ,否则孩子玩耍的区间就是 [ a i , n ] ⋃ [ 1 , b i ] [ai,n]⋃[1,bi] [ai,n][1,bi]

请帮助这只猴子找出两棵树,让他晨跑的时候他能够消耗最大的能量。

时间限制

2.00s

内存限制

500.00MB

思路

看到环形结构,第一个想法就是断环成链。
之后我们令, s u m i sum_i sumi表示从起点到 i i i的距离,也就是 d d d数组的前缀和,那么 2 ( h x + h y ) + d i s t ( x , y ) = 2 h x + 2 h y + s u m y − s u m x = s u m y + 2 h y − ( s u m x − 2 h x ) 2(hx+hy)+dist(x,y)=2hx+2hy+sum_y-sum_x=sum_y+2hy-(sum_x-2hx) 2(hx+hy)+dist(x,y)=2hx+2hy+sumysumx=sumy+2hy(sumx2hx),那么如果我们可以求出 s u m i + 2 h i sum_i+2hi sumi+2hi的最大值和 s u m i − 2 h i sum_i-2hi sumi2hi的最小值,就可以得到每次询问的答案。
可以使用线段树或者ST表维护这两个最值对应的下标,对于询问 ( l , r ) (l,r) (l,r),如果两个最值的下标相同,为 p p p,那么由于要求两颗不同的树,于是需要在 ( l , p − 1 ) (l, p - 1) (l,p1) ( p + 1 , r ) (p + 1, r) (p+1,r)中再查询一下,具体看代码啦,很简单滴。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 100005;

int d[N * 2], h[N * 2];
LL s1[N * 2], s2[N * 2];

struct Tree
{
    int l, r;
    int mxp, mip;
}t[N << 4];

int calcMx(int i, int j)
{
    return s1[i] > s1[j] ? i : j;
}

int calcMi(int i, int j)
{
    return s2[i] < s2[j] ? i : j;
}

void push_up(int i)
{
    t[i].mip = calcMi(t[i << 1].mip, t[i << 1 | 1].mip);
    t[i].mxp = calcMx(t[i << 1].mxp, t[i << 1 | 1].mxp);
}

void build(int i, int l, int r)
{
    t[i].l = l, t[i].r = r;
    if (l == r)
    {
        t[i].mxp = t[i].mip = l;
        return;
    }
    int mid = (l + r) >> 1;
    build(i << 1, l, mid);
    build(i << 1 | 1, mid + 1, r);
    push_up(i);
}

int queryMi(int i, int l, int r)
{
    if (l <= t[i].l && t[i].r <= r) return t[i].mip;
    int mid = (t[i].l + t[i].r) >> 1;

    if (l > mid) return queryMi(i << 1 | 1, l, r);
    else if(r <= mid) return queryMi(i << 1, l, r);
    else return calcMi(queryMi(i << 1, l, r), queryMi(i << 1 | 1, l, r));
}

int queryMx(int i, int l, int r)
{
    if (l <= t[i].l && t[i].r <= r) return t[i].mxp;
    int mid = (t[i].l + t[i].r) >> 1;

    if (l > mid) return queryMx(i << 1 | 1, l, r);
    else if(r <= mid) return queryMx(i << 1, l, r);
    else return calcMx(queryMx(i << 1, l, r), queryMx(i << 1 | 1, l, r));
}

int getMx(int l, int r)
{
    if (l > r) return 0;
    return queryMx(1, l, r);
}

int getMi(int l, int r)
{
    if (l > r) return 0;
    return queryMi(1, l, r);
}

LL solve(int l, int r)
{
    int mxp = getMx(l, r), mip = getMi(l, r);
    if (mip != mxp) return s1[mxp] - s2[mip];
    int mxpp = calcMx(getMx(l, mxp - 1), getMx(mxp + 1, r));
    int mipp = calcMi(getMi(l, mxp - 1), getMi(mxp + 1, r));
    return max(s1[mxp] - s2[mipp], s1[mxpp] - s2[mip]);
}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &d[i % n + 1]), d[i % n + 1 + n] = d[i % n + 1];
    for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]), h[i + n] = h[i];

    LL sum = 0;
    s1[0] = -1e18;
    s2[0] = 1e18;
    for (int i = 1; i <= n * 2; i ++ )
    {
        sum += d[i];
        s1[i] = sum + 2 * h[i];
        s2[i] = sum - 2 * h[i];
    }

    build(1, 1, 2 * n);

    for (int i = 1; i <= m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        if (l <= r) printf("%lld\n", solve(r + 1, l - 1 + n));
        else printf("%lld\n", solve(r + 1, l - 1));
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值