[Poi2004] 旅行问题
题目
Description
John 打算驾驶一辆汽车周游一个环形公路。公路上总共有n车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。
任务:判断以每个车站为起点能否按条件成功周游一周。
3<=n<=一百万
0≤p,d≤2×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; }