zoukankan      html  css  js  c++  java
  • [Poi2004] 旅行问题

    题目

    Description

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

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

    3<=n<=

    0p,d2×10^9

    Input

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

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

    Output

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

    Sample Input

    5
    3 1
    1 2
    5 2
    0 1
    5 4

    Sample Output

    TAK
    NIE
    TAK
    NIE
    TAK

    思路

    首先浅观此题,能够周游一圈满足的条件就是,在次过程中油量不为$0$;

    这一题比较麻烦的是,顺时针和逆时针都可以走,也就是我们需要正着做一遍,倒着也要做一遍;

    我们先看看正着怎么做;

    首先破环成列;

    那么每一次从第$i$个站到终点($i+n-1$), 就要保证 $p[i]-d[i] +$ 上一次剩余的油量 $geq 0$ ;

    那么我们可以设一个$sum[]$ 数组表示 $p[i]-d[i]$ 的前缀和;

    如果在由起点$i$到终点的过程中有 $sum[j]-sum[i-1]<0~~(i<j<=i+n-1) $,那么从 $i$ 出发就不能到达;

    所以我们可以维护一个单调队列;

    找出 $i$ 到 $i+n-1$ 里的 $min(sum[j])$ 如果$min(sum[j])-sum[i-1]<0$ ,那么从 $i$ 出发就不能到达;

    那么如果$min(sum[j])-sum[i-1] geq 0$ ,最小的 $sum$ 都可以到达,那么从$i$ 到其他点的距离就都可以到达;

    这样就$ok$ 了;

    那么反着做;

    需要注意的是

    $d[0]=d[n]$ ;

    $sum[i]=sum[i-1]+p[n-i+1]-d[n-i]~$ ;

    然后也没什么了;

    代码

    #include<bits/stdc++.h>
    #define re register//一片死寂 
    typedef long long ll;
    using namespace std;
    inline ll read()
    {
        ll a=0,f=1; char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
        while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
        return a*f;
    }
    ll n;
    ll p[1000010],d[1000010],a[1000010],b[1000010];
    ll sum[1000010],f[1000010];
    ll q[1000010];
    inline void doit()
    {
        for(re ll i=1;i<=2*n;i++)
            sum[i]=sum[i-1]+a[i];//统计前缀 
        ll head=1,tail=1;
        for(re ll i=1;i<=2*n;i++)//枚举终点 
        {
            while(head<=tail&&i-q[head]+1>n)//如果队列长度大于 n 
                head++;//踢队头 
            while(head<=tail&&sum[q[tail]]>sum[i])//找一个最小的sum[i] 
                tail--;//踢队尾 
            q[++tail]=i;//入队 
            if(i>=n)//终点大于 n  
            {
                if(sum[q[head]]-sum[i-n]>=0)//判断中途油量不为 0 
                    f[i-n+1]=1;//那么从 (i-n+1)这个起点可以到 i  
            }
        }
    }
    inline void doitagain()
    {
        memset(sum,0,sizeof(sum));//重置 
        for(int i=1;i<=2*n;i++)
            sum[i]=sum[i-1]+b[i];//统计前缀 
        ll head=1,tail=1;
        q[tail]=0;//重置 
        for(re ll i=1;i<=2*n;i++)
        {
            while(head<=tail&&i-q[head]+1>n)//如果队列长度大于 n 
                head++;//踢队头 
            while(head<=tail&&sum[q[tail]]>sum[i])//找一个最小的sum[i] 
                tail--;//踢队尾 
            q[++tail]=i;//入队 
            if(i>=n)//终点大于 n 
            {
                if(sum[q[head]]-sum[i-n]>=0)//中途油量不为 0 
                    f[2*n-i]=1;// 这个地方自己可以画个样例判断,
                               //我就不多解释了 
            }
        }
    }
    int main()
    {
        n=read();//读入 n  
        for(re ll i=1;i<=n;i++)
        {
            p[i]=read();
            d[i]=read();
            a[i]=p[i]-d[i];//计算差值,方便统计 sum[i] 
            a[i+n]=a[i];//破环成列 
        }
        d[0]=d[n];//破环成列 
        doit();
        for(re ll i=1;i<=n;i++)
        {
            b[i]=p[n-i+1]-d[n-i];//从后往前走 
            b[n+i]=b[i];//破环成列 
        }
        doitagain();
        for(re ll i=1;i<=n;i++)
        if(f[i])//输出 
            puts("TAK");//TAK!!! 
        else
            puts("NIE");//NIE ~( ̄▽ ̄)~* 
        //return 0; 
    }
  • 相关阅读:
    小程序开发过程中遇到的问题
    Windows 常用命令与快捷键
    前端开发中遇到的问题记录
    判断当前页面是否在微信中
    js学习导图
    一篇不错的 文章
    flex 布局
    微信客户端sdk使用前的 授权签名
    elementUI+vue-cli el-table=》excel
    rem适配
  • 原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13455743.html
Copyright © 2011-2022 走看看