UVA11093 环形跑道 Just Finish it up
written on 2022-06-06
一题多解。
第一种方法比较直观而且暴力,其关键在于转化题意。此题中的关键,在于对每一个起点 \(i\) 的判定。如果暴力 \(O(n)\) 扫一遍,总时间复杂度就会是 \(O(n^2)\),超时,因此考虑转化题意后用数据结构优化。
(下述 \(p\) 表示 \(p\) 原意义的前缀和,\(q\) 同理)
我们可以发现,对于一个起点 \(i\) ,如果它满足题意,那么就有:
-
\(\forall j\in \left[i,n\right]\),\(p_j-p_{i-1}\geq q_j-q_{i-1}\),将常量与变量分别移到不等式两边,就有\(p_j-q_j\geq p_{i-1}-q_{i-1}\),也就是说对于所有在 \(i\) 右边的点都能顺利开到下一个点。数学上来看,区间内所有数都满足大于等于的条件,那么就要求最小值也满足,因此我们只需要用线段树来维护即可。
-
\(\forall j\in \left[1,i-1\right]\),\(p_n-p_{i-1}+p_j\geq q_n-q_{i-1}+q_j\)。式子意义、化简,以及维护方法均与上同,不再赘述。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n;
int a[N],b[N],P[N],Q[N];
struct Seg
{
int val[N<<2];
void build(int p,int l,int r)
{
val[p]=1e9;
if(l==r)
{
val[p]=P[l]-Q[l];
return ;
}
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
val[p]=min(val[p<<1],val[p<<1|1]);
}
int ask(int p,int l,int r,int L,int R)
{
if(L>R) return 1e9;
if(L<=l&&R>=r) return val[p];
int mid=l+r>>1,res=1e9;
if(L<=mid) res=min(res,ask(p<<1,l,mid,L,R));
if(R>mid) res=min(res,ask(p<<1|1,mid+1,r,L,R));
return res;
}
}t1;
int main()
{
int T;
scanf("%d",&T);
for(int rnd=1;rnd<=T;rnd++)
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),P[i]=P[i-1]+a[i];
for(int i=1;i<=n;i++) scanf("%d",&b[i]),Q[i]=Q[i-1]+b[i];
t1.build(1,1,n);
bool flag=0;
for(int i=1;i<=n;i++)
{
if(a[i]<b[i]) continue;//小小的优化
int Rmn=t1.ask(1,1,n,i,n);
if(Rmn<P[i-1]-Q[i-1]) continue;
int Lmn=t1.ask(1,1,n,1,i-1);
if(Lmn<Q[n]-Q[i-1]-P[n]+P[i-1]) continue;
// printf("i=%d Lmn=%d Rmn=%d\n",i,Lmn,Rmn);
printf("Case %d: Possible from station %d\n",rnd,i);
flag=1;
break;
}
if(!flag) printf("Case %d: Not possible\n",rnd);
}
}
第二种方法也是源于暴力的思考,为了保证时间复杂度,我们希望能够在线性扫的同时排除不可能作为起点的那些点。恰好在本题中,有一个显然的结论,也就是:从点 \(i\) 出发,如果在点 \(j\) 没油了,那么\(\left[i,j\right]\)内的所有点均不可能作为起点。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n;
int a[N],b[N],st;
int work(int x,int now,int dep)
{
if(x>n) x-=n;
if(now<0) return x;
if(dep==n) return -1;
return work(x+1,now+a[x]-b[x],dep+1);
}
int main()
{
int T;
scanf("%d",&T);
for(int rnd=1;rnd<=T;rnd++)
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
st=1;
int ans=-1;
while(1)
{
int ed=work(st,0,0);
if(ed==-1)
{
ans=st;
break;
}
if(ed<=st) break;
st=ed;
}
if(ans==-1) printf("Case %d: Not possible\n",rnd);
else printf("Case %d: Possible from station %d\n",rnd,ans);
}
}
第二种方法要比第一种快,理论与实践皆是如此。