UVA11093 环形跑道 Just Finish it up

written on 2022-06-06

一题多解。

第一种方法比较直观而且暴力,其关键在于转化题意。此题中的关键,在于对每一个起点 \(i\) 的判定。如果暴力 \(O(n)\) 扫一遍,总时间复杂度就会是 \(O(n^2)\),超时,因此考虑转化题意后用数据结构优化。

(下述 \(p\) 表示 \(p\) 原意义的前缀和,\(q\) 同理)

我们可以发现,对于一个起点 \(i\) ,如果它满足题意,那么就有:

  1. \(\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\) 右边的点都能顺利开到下一个点。数学上来看,区间内所有数都满足大于等于的条件,那么就要求最小值也满足,因此我们只需要用线段树来维护即可。

  2. \(\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);
	}
}

第二种方法要比第一种快,理论与实践皆是如此。

posted @ 2022-07-31 21:44  Freshair_qprt  阅读(15)  评论(0编辑  收藏  举报