AcWing 1088 旅行问题

题目描述:

John 打算驾驶一辆汽车周游一个环形公路。

公路上总共有 n 个车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。

John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。

在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。

任务:判断以每个车站为起点能否按条件成功周游一周。

输入格式

第一行是一个整数 n,表示环形公路上的车站数;

接下来 n 行,每行两个整数 pi,di,分别表示表示第 i 号车站的存油量和第 i 号车站到下一站的距离。

输出格式

输出共 n 行,如果从第 i 号车站出发,一直按顺时针(或逆时针)方向行驶,能够成功周游一圈,则在第 i 行输出 TAK,否则输出 NIE。

数据范围

3≤n≤10^6,
0≤pi≤2×10^9,
0<di≤2×10^9

输入样例:

5
3 1
1 2
5 2
0 1
5 4

输出样例:

TAK
NIE
TAK
NIE
TAK

分析:

 这题是y总的思路随便听听,然后就按照自己思路写了,代码可能与y总思路有所出入。

首先,既然是环形问题,自然按照处理环形问题的一般思路,展开成两倍长度的线性序列。比如n = 5,就可以展开成1,2,3,4,5,1,2,3,4,5,这些序列的编号从1到10,则求从1顺时针出发能否回到1,就是求从编号为1的点出发能否到达编号为6的点,求从5逆时针出发能否回到5,就是求从编号为10的点出发能否到达编号为5的点。

能否到达取决于剩余油量是否够。p[i]表示在第i个加油站可以加的油量,d[i]表示从第i个点走到第i + 1个点的路程,则p[i] - d[i]就表示从第i个点到第i+1个点油量的增减情况,先考虑顺时针的情况,s[i] = s[i-1] + p[i] - d[i]表示剩余油量的前缀和,也就是说s[i]表示刚走到第i + 1个点还未加油时汽车的剩余油量,如果油量是负数,就说明不够到达。当然,在代码中的含义与这里有所不同,因为我们要求从编号为1的点到编号为2n的点的前缀和。我们需不需要每到一个站就判断下剩余油量呢,显然不需要这样做,只需要回到起点时回顾下全程的最低油量是否小于0即可。从1出发,顺时针走到n,我们需要求s[1]-s[0],s[2]-s[0],...,s[n]-s[0]中的最小值,也就是说,求一个长度为n的区间的最小值,所以可以使用单调队列优化。

 for(int i = 1;i <= 2 * n;i++)   s[i] = p[i] - d[i] + s[i - 1];//顺时针
    int hh = 0,tt = 0;
    for(int i = 1;i <= 2 * n - 1;i++){
        if(i - q[hh] >= n)  hh++;
        while(hh <= tt && s[q[tt]] >= s[i]) tt--;
        q[++tt] = i;
        if(i >= n && s[q[hh]] - s[i - n] >= 0)  ans[i - n + 1] = true;
    }

 首先观察下顺时针的单调队列代码,由于遍历到s[n]时表示走到了第i + 1个点剩余的油量,所以i大于等于n时就可以统计区间长度为n的最小值了,意味着i=n + 1时,就到达了从2顺时针出发的终点,i = 1就不应该出现在队列里了,所以i - q[hh] >= n时出队头,我们要找的是区间中的最小值,所以队头元素需要最小,因此当s[i]小于等于队尾元素时,需要出队尾。正如上面所说,i = n时,实际上是到达了从1顺时针出发的终点,我们将这个区间中s的最小值s[q[hh]]减去起点前的前缀和s[i-n]就表示这段区间的最低油量了,只要它不是负数,就说明从这个区间的起点出发可以到达终点,终点是i + 1,起点就是i - n + 1,。

下面考虑逆时针的代码,你甚至需要倒着枚举序列,因此需要后缀和。

s[2 * n + 1] = 0;//逆时针
    for(int i = 2* n;i >= 1;i--)   s[i] = p[i] - d[i - 1] + s[i + 1];
    hh = 0,tt = 0,q[tt] = 2 * n + 1;
    for(int i = 2 * n;i >= 2;i--){
        if(q[hh] - i >= n)  hh++;
        while(hh <= tt && s[q[tt]] >= s[i]) tt--;
        q[++tt] = i;
        if(i <= n + 1 && !ans[i - 1] && s[q[hh]] - s[i + n] >= 0)   ans[i - 1] = true;
    }

 第一个要考虑的起点是编号为2n的点,也就是原序列中编号为n的点,这里的s[i]同样表示从第i个点出发逆时针到达下一个加油站的剩余油量,此时的s[i] = s[i+1] + p[i] - d[i-1],因为第i个加油站能加的油量是p[i],而从第i个点到达第i-1个点的距离是d[i-1]。逆时针的单调队列的写法与顺时针的基本一致,同样用特殊的例子改下边界即可。从2n出发,当i = n + 1也就是到达了终点n,一旦队列的队头减去i达到了n,就需要出队头,出队尾的操作与顺时针的一样,都是找区间的最小值,第一个终点是n,此时i = n + 1,所以i = n + 1后开始统计最小值,此时区间的起点恰好是i - 1,再判断下这段区间的最小值是否小于0即可。

总的代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2000005;
typedef long long ll;
int p[N],d[N],q[N];
ll s[N];
bool ans[N];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        scanf("%d%d",&p[i],&d[i]);
        p[i + n] = p[i],d[i + n] = d[i];
    }
    for(int i = 1;i <= 2 * n;i++)   s[i] = p[i] - d[i] + s[i - 1];//顺时针
    int hh = 0,tt = 0;
    for(int i = 1;i <= 2 * n - 1;i++){
        if(i - q[hh] >= n)  hh++;
        while(hh <= tt && s[q[tt]] >= s[i]) tt--;
        q[++tt] = i;
        if(i >= n && s[q[hh]] - s[i - n] >= 0)  ans[i - n + 1] = true;
    }
    s[2 * n + 1] = 0;//逆时针
    for(int i = 2* n;i >= 1;i--)   s[i] = p[i] - d[i - 1] + s[i + 1];
    hh = 0,tt = 0,q[tt] = 2 * n + 1;
    for(int i = 2 * n;i >= 2;i--){
        if(q[hh] - i >= n)  hh++;
        while(hh <= tt && s[q[tt]] >= s[i]) tt--;
        q[++tt] = i;
        if(i <= n + 1 && !ans[i - 1] && s[q[hh]] - s[i + n] >= 0)   ans[i - 1] = true;
    }
    for(int i = 1;i <= n;i++){
        if(ans[i])  printf("TAK\n");
        else    printf("NIE\n");
    }
    return 0;
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值