刷题记录(NC16562 开车旅行)(树上倍增)

NC16562 开车旅行

题目链接

关键点:

1、预处理:将所有点到达另一个点的最短和次短距离先求出

方法:利用set,从最后一个城市开始插入,每次插入到set中,查看左右是否存在有城市(即set是按照海拔高度从小到大排序的),要查看左右的旁边和再旁边是否存在城市,然后更新每个点的次短和最短距离

说明下数组:dis[i][j]表示从i点出发,j=0表示最短距离点,j=1表示次短距离点,nxt[i][j]表示从i点出发,j=0表示最短距离,j=1表示次短距离

代码中有详细的解释

    for (int i=n; i>=1; i--)
    {
        s.insert(city[i]);
        it = s.find(city[i]);//将城市进行实时按照海拔排序,方便求出左右的距离
        if (it != s.begin())//若存在比当前城市海拔小的城市,更新最短次短距离
        {
            it--;
            update(city[i], *it);
            if (it != s.begin())//继续更新
            {
                it--;
                update(city[i], *it);
                it++;
            }
            it++;//还原it,重新指向city[i]
        }
        if ((++it) != s.end())//若此时存在比city[i]海拔大的城市
        {
            update(city[i], *it);
            ++it;
            if (it != s.end())//存在次短
            {
                update(city[i], *it);
                it--;
            }
            it--;//还原it
        }
    }

update函数

void update(ty t1, ty t2)
{
    if (!nxt[t1.num][0])//如果t1点的最短点还没求出
    {
        nxt[t1.num][0] = t2.num;
        dis[t1.num][0] = abs(t1.h - t2.h);
    }
    else if (dis[t1.num][0]>abs(t1.h-t2.h) || ((dis[t1.num][0] == abs(t1.h - t2.h)) && (city[nxt[t1.num][0]].h > t2.h)))//如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
    {
        nxt[t1.num][1] = nxt[t1.num][0];//从前的最短点变成次短点
        dis[t1.num][1] = dis[t1.num][0];
        nxt[t1.num][0] = t2.num;//更新最短点
        dis[t1.num][0] = abs(t1.h - t2.h);
    }
    else if (dis[t1.num][1]>abs(t1.h-t2.h) || ((dis[t1.num][1] == abs(t1.h - t2.h)) && (city[nxt[t1.num][1]].h > t2.h)))//此时的短是比次短点短,如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
    {
        nxt[t1.num][1] = t2.num;//更新次短点
        dis[t1.num][1] = abs(t1.h - t2.h);
    }
    else if (!nxt[t1.num][1])//若此时没有次短点
    {
        nxt[t1.num][1] = t2.num;//更新次短点
        dis[t1.num][1] = abs(t1.h - t2.h);
    }
}

2、然后是利用两个数组,进行倍增。g[i][j]维护从i点出发,开2^j轮走到的点(A先开)

f[i][j][0/1]维护从i点出发,走2^j次方轮,0表示A走的次短距离,1表示B走的次短距离

初始化

    for (int i=1; i<=n; i++)
    {
        g[i][0] = nxt[nxt[i][1]][0];//从i城市出发,走一轮(A、B各走一次)到达的点(因为A先开车)
        f[i][0][0] = dis[i][1];//从i点出发,走一轮,A行驶路程
        f[i][0][1] = dis[nxt[i][1]][0];//从i点出发,走一轮,B行驶路程
    }

然后

    for (int j=1; j<=20; j++)
    {
        for (int i=1; i<=n; i++)
        {
            g[i][j] = g[g[i][j-1]][j-1];//从i点出发走2^j次方轮到达的点为 从i点出发走2^(j-1)次方轮到达的点再走2^(j-1)次方轮到达的点
            f[i][j][0] = f[i][j-1][0] + f[g[i][j-1]][j-1][0];//A点从i点出发走2^j轮行驶的路程 = A点从i点出发走2^(j-1)轮走的路程 + 从2^(j-1)次方轮点走2^(j-1)次方轮A行驶的路程
            f[i][j][1] = f[i][j-1][1] + f[g[i][j-1]][j-1][1];//和上面类似
        }
    }

3、对于询问一,给出x0,枚举每个城市,求出a行驶的路程和b行驶的路程,然后进行比值计算(比值计算可以换成乘法计算),那么这个根据出发城市s,总行驶路程x,求出a行驶的路程和b行驶的路程这个过程在询问二中也有出现,因此可以维护成一个函数

void query(int s, int x, ll &disa, ll &disb)//出发城市为s,最大行驶路程为x,a行驶的距离为disa,b行驶的距离为disb
{
    for (int i=20; i>=0; i--)
    {
        if (f[s][i][0] + f[s][i][1] <= x && (g[s][i]))//如果两人行驶的总路程小于x,并且两人都可以到达下一个点
        {
            disa += f[s][i][0];//更新A、B行驶的总路程
            disb += f[s][i][1];
            x -= (f[s][i][0] + f[s][i][1]);//总行驶距离更新
            s = g[s][i];//下一轮的出发点更新
        }
    }
    if (nxt[s][1] && dis[s][1] <= x)//若此时只能A走一步
        disa += dis[s][1];
}

完整代码(详细注释)

# include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000+10;
ll dis[N][2], nxt[N][2], f[N][21][2], g[N][21];//dis表示从i点出发,j=0表示最短距离点,j=1表示次短距离点,nxt表示从i点出发,j=0表示最短距离,j=1表示次短距离;f表示从i点出发,走2^j次方轮,0表示A走的次短距离,1表示B走的次短距离,g表示从i点出发,开2^j轮走到的点(A先开)
struct ty{
    int h;
    int num;
    bool operator < (const ty a) const{
        return h<a.h;
    }
}city[N];//存储每个城市的海拔点
set<ty>s;
set<ty>::iterator it;
int n, m;
void update(ty t1, ty t2)
{
    if (!nxt[t1.num][0])//如果t1点的最短点还没求出
    {
        nxt[t1.num][0] = t2.num;
        dis[t1.num][0] = abs(t1.h - t2.h);
    }
    else if (dis[t1.num][0]>abs(t1.h-t2.h) || ((dis[t1.num][0] == abs(t1.h - t2.h)) && (city[nxt[t1.num][0]].h > t2.h)))//如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
    {
        nxt[t1.num][1] = nxt[t1.num][0];//从前的最短点变成次短点
        dis[t1.num][1] = dis[t1.num][0];
        nxt[t1.num][0] = t2.num;//更新最短点
        dis[t1.num][0] = abs(t1.h - t2.h);
    }
    else if (dis[t1.num][1]>abs(t1.h-t2.h) || ((dis[t1.num][1] == abs(t1.h - t2.h)) && (city[nxt[t1.num][1]].h > t2.h)))//此时的短是比次短点短,如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
    {
        nxt[t1.num][1] = t2.num;//更新次短点
        dis[t1.num][1] = abs(t1.h - t2.h);
    }
    else if (!nxt[t1.num][1])//若此时没有次短点
    {
        nxt[t1.num][1] = t2.num;//更新次短点
        dis[t1.num][1] = abs(t1.h - t2.h);
    }
}
void query(int s, int x, ll &disa, ll &disb)//出发城市为s,最大行驶路程为x,a行驶的距离为disa,b行驶的距离为disb
{
    for (int i=20; i>=0; i--)
    {
        if (f[s][i][0] + f[s][i][1] <= x && (g[s][i]))//如果两人行驶的总路程小于x,并且两人都可以到达下一个点
        {
            disa += f[s][i][0];//更新A、B行驶的总路程
            disb += f[s][i][1];
            x -= (f[s][i][0] + f[s][i][1]);//总行驶距离更新
            s = g[s][i];//下一轮的出发点更新
        }
    }
    if (nxt[s][1] && dis[s][1] <= x)//若此时只能A走一步
        disa += dis[s][1];
}
int main()
{
    scanf("%d", &n);
    for (int i=1; i<=n; i++)
    {
        scanf("%d", &city[i].h);
        city[i].num = i;
    }
    
    for (int i=n; i>=1; i--)
    {
        s.insert(city[i]);
        it = s.find(city[i]);//将城市进行实时按照海拔排序,方便求出左右的距离
        if (it != s.begin())//若存在比当前城市海拔小的城市,更新最短次短距离
        {
            it--;
            update(city[i], *it);
            if (it != s.begin())//继续更新
            {
                it--;
                update(city[i], *it);
                it++;
            }
            it++;//还原it,重新指向city[i]
        }
        if ((++it) != s.end())//若此时存在比city[i]海拔大的城市
        {
            update(city[i], *it);
            ++it;
            if (it != s.end())//存在次短
            {
                update(city[i], *it);
                it--;
            }
            it--;//还原it
        }
    }
    
    for (int i=1; i<=n; i++)
    {
        g[i][0] = nxt[nxt[i][1]][0];//从i城市出发,走一轮(A、B各走一次)到达的点(因为A先开车)
        f[i][0][0] = dis[i][1];//从i点出发,走一轮,A行驶路程
        f[i][0][1] = dis[nxt[i][1]][0];//从i点出发,走一轮,B行驶路程
    }
    for (int j=1; j<=20; j++)
    {
        for (int i=1; i<=n; i++)
        {
            g[i][j] = g[g[i][j-1]][j-1];//从i点出发走2^j次方轮到达的点为 从i点出发走2^(j-1)次方轮到达的点再走2^(j-1)次方轮到达的点
            f[i][j][0] = f[i][j-1][0] + f[g[i][j-1]][j-1][0];//A点从i点出发走2^j轮行驶的路程 = A点从i点出发走2^(j-1)轮走的路程 + 从2^(j-1)次方轮点走2^(j-1)次方轮A行驶的路程
            f[i][j][1] = f[i][j-1][1] + f[g[i][j-1]][j-1][1];//和上面类似
        }
    }
    int x0;
    scanf("%d", &x0);
    int s0 = 0;
    ll a = 1e15, b = 0;
    for (int i=1; i<=n; i++)//枚举每个出发城市
    {
        ll disa = 0, disb = 0;
        query(i, x0, disa, disb);
        if (disb && (!s0 || disa*b < disb*a))//若disb不为0(若为0,比值为无穷大),且disa/disb会小(比值比较换成分母分子相乘比较,先初始化a/b = 无穷大,则满足disa/disb < a/b 即disa*b < disb*a)
        {
            s0 = i;//更新最小比值城市
            a = disa;//更新比值
            b = disb;
        }
    }
    cout<<s0<<endl;
    scanf("%d", &m);
    for (int i=1; i<=m; i++)
    {
        int s, x;
        ll disa = 0, disb = 0;
        scanf("%d%d", &s, &x);
        query(s, x, disa, disb);
        cout<<disa<<" "<<disb<<endl;
    }
    
    
    return 0;
}    

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值