[POI2005]A Journey to Mars 单调队列

本人水平有限,题解不到为处,请多多谅解

本蒟蒻谢谢大家观看

题目:传送门

首先考虑一个问题:跑一圈都是跑的相同的一圈,为什么有的点开始就能跑完,有的点开始就跑不完呢。

比如第3个点跑不完,是“NIE”。第1个点能跑完,是“TAK”。

第3个点跑不完,一定是在半路上某两个点中间死掉了。如果我们给每个点设一个“濒死值”,表示刚到某个点,还没有拿这个点的油时,油的剩余量。死掉了就是中间有某个点的“濒死值”小于0了。

而从第1个点跑过来时汽油还有些剩余,正是这些“剩余”,帮它度过了那两个点之间的艰难岁月。

只要度过了最黑暗的一段,其他的就都不是问题了。

因此,问题转化为了,每个点在初始没有结余的情况下,能否熬过以它开始的一圈中汽油最少的一段(濒死值最小的一个点)。

所以可以从1开始暴力模拟一遍,求出以1为起点的每个点的“濒死值”。因为是环,所以可以在n后面再补一段1~n。把这段长为 2*n 的路走一遍。 看一下每个点走到自己对应的点的路上最小的濒死值的最小值是否大于这个点的剩余(也就是这个点的濒死值),就可以判断是否能从这个点开始走完一圈了。

现在,唯一的问题变为了,如何在一个长为 2n的序列中,求出一些长为 n的段的最小值。

证明过程:

设a[i]为i的油量,b[i]为i到i+1或当i=n时n到1的路径长度。
我们先考虑一个方向。
如果1满足要求,应满足哪些条件?
a[1]≥b[1]
a[1]+a[2]≥b[1]+b[2]
a[1]+a[2]+a[3]≥b[1]+b[2]+b[3]
......
变一下
a[1]−b[1]>=0
a[1]−b[1]+a[2]−b[2]>=0
a[1]−b[1]+a[2]−b[2]+a[3]−b[3]>=0
......
设v[i]=a[i]−b[i]

v[1]≥0

v[1]+v[2]≥0
v[1]+v[2]+v[3]≥0
......
我们设
sum[i]=∑ij=1 v[j]
则若ii满足要求,
sum[i]−sum[i−1]≥0
sum[i+1]−sum[i−1]≥0
sum[i+2]−sum[i−1]≥0
......
我们不妨把v数组倍长一下。

min(sum[i],sum[i+1],sum[i+2]...sum[i+n−1])≥sum[i−1]

转换成了单调队列求最小值

只需要正的跑一边,在反向跑一遍(题目要求初始时可以任意两个方向)

code:

 1 #include<bits/stdc++.h>
 2 #pragma GCC optimize(3)
 3 const int N=2001000;
 4 using namespace std;
 5 int n,p[N],d[N],sum[N],num[N],Fmin[N];
 6 int head,tail,s;
 7 bool flag[N];
 8 inline int read(){
 9     int x=0,f=1;char ch=getchar();
10     while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
11     while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
12     return x*f;
13 }
14 inline void write(int x){
15      char F[200];
16      int tmp=x>0?x:-x ;
17      if(x<0)putchar('-') ;
18      int cnt=0 ;
19         while(tmp>0)
20         {
21             F[cnt++]=tmp%10+'0';
22             tmp/=10;
23         }
24         while(cnt>0)putchar(F[--cnt]) ;
25 }
26 int main()
27 {
28     memset(flag,false,sizeof(flag));
29     n=read();
30     for(int i=1;i<=n;i++){
31         p[i]=read(),d[i]=read();
32         p[i+n]=p[i],d[i+n]=d[i];
33     }
34     memset(sum,0,sizeof(sum));
35     for(int i=1,s=0;i<=2*n;i++){
36         sum[i]=s,s+=p[i]-d[i];
37         //cout<<"sum[i]= "<<sum[i]<<" i= "<<i<<endl;
38     }
39     head=0,tail=0;
40     for(int i=1;i<=2*n;i++){
41         while(head<=tail&&sum[i]<=sum[num[tail]])tail--;
42         num[++tail]=i;
43         while(head<=tail&&num[tail]-num[head]>=n)head++;
44         Fmin[i-n]=sum[num[head]];
45     }
46     for(int i=1;i<=n;i++){
47         if(Fmin[i]-sum[i]>=0)flag[i]=true;
48     }
49     memset(sum,0,sizeof(sum));
50     for(int i=2*n,s=0;i>=1;i--){
51         sum[i]=s,s+=p[i]-d[i-1];
52     }
53     head=0,tail=0;
54     for(int i=2*n;i>=1;i--){
55         while(head<=tail&&sum[i]<=sum[num[tail]])tail--;
56         num[++tail]=i;
57         while(head<=tail&&num[head]-num[tail]>=n)head++;
58         Fmin[i]=sum[num[head]];
59     }
60     for(int i=1;i<=n;i++){
61         if(Fmin[i]-sum[i+n]>=0)flag[i]=true;
62     }
63     for(int i=1;i<=n;i++){
64         if(flag[i]){
65             printf("TAK
");
66         }
67         else{
68             printf("NIE
");
69         }
70     }
71     return 0;
72 }
73 /*
74 5
75 3 1
76 1 2
77 5 2
78 0 1
79 5 4
80 */ 
原文地址:https://www.cnblogs.com/nlyzl/p/11684610.html