【算法•日更•第十八期】信息奥赛一本通1600:【例 4】旅行问题题解

  废话不多说,直接上题:


1600:【例 4】旅行问题


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 96     通过数: 36 

【题目描述】

原题来自:POI 2004

John 打算驾驶一辆汽车周游一个环形公路。公路上总共有 n 车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。

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

【输入】

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

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

【输出】

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

【输入样例】

5
3 1
1 2
5 2
0 1
5 4

【输出样例】

TAK
NIE
TAK
NIE
TAK

【提示】

数据范围与提示:

对于全部数据,3≤n≤106,0≤pi≤2×109,0<di≤2×109 。

【来源】


  这道题先不管其他的,先来说怎么处理这个绕圈(环)的问题。

  当然这是很简单的,只要破环成链就可以了,方法很粗暴,直接开到二倍就可以了。

  什么意思呢?比如说数据是1,2,3,4,5的环,那么我们就可以改成1,2,3,4,5,1,2,3,4,5的链,这样任何的搭配方式都可以体现出来,只要注意不要多出来就可以了。

  好了,回归这道题,如何解决?方法有三种:

  方法1:直接暴力,模拟旅行的过程

  显然,时间复杂度是O(n2)的,像这道题的超大的数据规模,肯定无法接受。

  方法2:使用堆来优化

  为什么上面的办法不行?因为我们没有合理的利用已经算过的东西。而且题目中涉及到了p和d,我们不妨合并成一个a数组,使a[i]=p[i]-d[i],表示油量的增减情况。

  我们可以维护一个前缀和pre来存储,那么我们要走的路就可以通过差分来求出,我们要判断一条路能否走成功,那么就要看这条路中油最少的情况是否大于0。

  那么我们就可以使用堆来存储,整个算法时间复杂度是O(n log n),但是对于这么大的数据规模来说还是太勉强。

  方法3:还有其他方法,例如RMQ,不过小编不会

  方法4:单调队列优化动态规划

  虽然我看不出来哪里有动态规划,但是单调队列是必须要用的,我们可以把2*n-1当作n,把n当作k,把每一次到加油站的油当作数,这样就是单调队列的模板(定长连续子区间的最值问题)。

  不过这些都很easy,但是怎样能够处理顺时针和逆时针呢?

  这是一个棘手的问题,先来看下面的示例,比方说数据是这样的:

  

  那么逆时针的顺序就是这样的:

  

  经过探寻规律得:p1[i]=p[n-i+2],d1[i]=d[n-i+1],只要这样处理后就可以像顺时针一样再算了。

  不过要注意边界值,p1[1]=1,d1[1]=d[n],不信你试试,按上面算出来的会有问题。

  详见注释,代码如下:

#include<iostream>
using namespace std;
long long n,p[3000000],d[3000000],pre[3000000],num[3000000],a[3000000],q[3000000],ans[3000000],p1[3000000],d1[3000000];
long long number(long long x)//判断逆时针前的位置
{
    if(x==n) return 1;
    else return 2*n-x+1;
}
inline void change()//改成逆时针
{
    p1[1]=p[1];d1[1]=d[n];
    a[1]=p1[1]-d1[1];
    pre[1]=a[1];
    for(long long i=2;i<=n;i++)
    {
        p1[i]=p[n-i+2];
        d1[i]=d[n-i+1];
        a[i]=p1[i]-d1[i];
        a[i+n]=a[i];
        pre[i]=pre[i-1]+a[i];
    }
    for(long long i=n+1;i<=2*n;i++)
    pre[i]=pre[i-1]+a[i];
}
inline void dp()
{
    long long head=1;long long tail=0;
    for(long long i=1;i<=2*n-1;i++)//顺时针
    {
        while(num[head]<i-n+1&&head<=tail) head++;
        while(pre[i]<=q[tail]&&head<=tail) tail--;
        q[++tail]=pre[i];
        num[tail]=i;
        if(i>=n) 
        {
            if(q[head]-pre[i-n]<0) ans[i-n+1]=0;
            else ans[i-n+1]=1;
        }
    }
    change();
    head=1;tail=0;
    for(long long i=1;i<=2*n-1;i++)//逆时针
    {
        while(num[head]<i-n+1&&head<=tail) head++;
        while(pre[i]<=q[tail]&&head<=tail) tail--;
        q[++tail]=pre[i];
        num[tail]=i;
        if(i>=n) 
        if((q[head]-pre[i-n])>=0) ans[number(i)]=1;
    }
}
int main()
{
    cin>>n;
    for(long long i=1;i<=n;i++)
    {
        cin>>p[i]>>d[i];
        a[i]=p[i]-d[i];
        a[i+n]=a[i];
        pre[i]=pre[i-1]+a[i];//记录前缀和
    }
    for(long long i=n+1;i<=2*n;i++)
    pre[i]=pre[i-1]+a[i];
    dp();
    for(long long i=1;i<=n;i++)
    if(ans[i]==1) cout<<"TAK"<<endl;
    else cout<<"NIE"<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/TFLS-gzr/p/11217331.html