地铁导航(前缀和)

题目描述

最近小王所在的城市在修建地铁,已经有很多的地铁已经完工,但也有一些还在施工中。现在小王要出发去参加朋友的聚会,在出行时会尽可能的节省时间,地铁的速度非常快,假设每公里只需3分钟,步行的话每公里需要20分钟。

出行小王从家里出发,通过导航发现,到达目的地有 n 条路,从导航来看到每个目的地的时间都差不多,但是导航的数据并未实时更新,有些地方在修建地铁所以改变走不通,要绕原路,绕路每公里30分钟。

如果时间足够的话,小王可以慢慢计算哪一条最快,可惜聚会就要开始了,小王不得不选取一条导航显示最快的一条。

  • 如果 i 号点有地铁已完工,那么可以从 i - 1 号点做地铁到 i 号点;
  • 如果 i 号点有地铁未完工,那么可以从 i - 1 号点绕远路到 i 号点;
  • 如果 i 号点没有地铁,那么可以从 i - 1 号点不行到 i 号点;

输入

  • 第一行输入 L,M。分别表示所选道路的长度和道路中地铁的数量;
  • 接下来M行,为每个地铁的信息,每行3个数,x,l,r。分别表示 地铁是否在完工(0 未完工 ,1 以完工),l ,r 表示地铁的范围;

输出

  • 输出到达目的的时间;

样例

输入数据 1

50 3
1 1 20
0 21 30
1 40 50

输出数据 1

573

样例1解析

  • 小王处在 0 的位置 , 坐地铁到 20 ,路径 20 公里 ,共耗时 20 * 3 = 60;
  • 小王处在 20 的位置 , 中间修地铁,绕路 10 公里到 30 的位置, 共耗时 10 * 30 + 60 = 360;
  • 小王处在 30 的位置, 步行到 39 的位置,路径 9 公里 ,共耗时 9 * 20 + 360 = 540;
  • 小王处在 39 的位置, 坐地铁到 50 ,路径 11 公里 , 共耗时 11 * 3 + 540 = 573;

 ---------------------------------------------------------

分析

第一种方法:从题目来看,通过站点有三种可能。

        1)走路

        2)绕路

        3)坐地铁

        所以每个站点一定会是三个中的其中一种,所以我们可以通过标记状态的方法,然后通过判断每个站点状态来做相应的累加。

        因为这个题目数据比较大,这种方法虽然能得80分,只能说明数据很水,因为题目毕竟要求有地铁就要坐地铁,而不能以最后一次标注状态为准。

        而且这种方法还会出现超时,毕竟算法复杂度有点高。

默认走路:全部标记为0

需要绕路:标记为1

坐地铁:标记为2

    Code

#include<bits/stdc++.h>
using namespace std;
int a[1000001];
int main(){
	memset(a,0,sizeof(a));
	int dis,q;
	cin>>dis>>q;
	for(int i=1;i<=q;i++){
		int x,l,r;
		cin>>x>>l>>r;
		for(int j=l;j<=r;j++){
			if(x==0) a[j]=1;
			else a[j]=2;
		}
	}
	long long ans=0;
	for(int i=1;i<=dis;i++){
		if(a[i]==0) ans+=20;
		if(a[i]==1) ans+=30;
		if(a[i]==2) ans+=3;
	}
	cout<<ans;
}

------------------------------------------

下面再说明一下第二种方法:这种方法能明显降低算法复杂度。

就是利用差分前缀和的特性,只要首位加1,那么前缀和区间都能实现加1。对于下一个区间的前缀和,不能将前一个区间加的累加到下一个区间,所以在区间首位置加1后,结束位置我们要减才可以。

例如:a[]={0,1,2,3,4},前缀和s[]

如果首位置不加1,那么前缀和s1=1,s2=3,s3=6,s4=10

a1=1+1=2  //如果想要区间[1,3]的前缀和都加1,那么只要对首位a1加1就可,但是s4不想增加1,就要将a4-1才可。

s1=a1+a0=2+0=2

s2=s1+a2=2+2=4

s3=s2+a3=4+3=7

a4=a4-1=3  // a4减去1,那么后面区间就不会因为前面区间加1而受影响。

s4=s3+a4=7+3=10

可以利用这个特性,可以对输入l,r进行操作。

定于用于标注状态的数组a[]

对于当x==1的时候,a[l]=a[l]+1,a[r+1]=a[r+1]-1

对于当x==0的时候,a[l]=a[l]+100000,a[r+1]=a[r+1]-100000

后面通过求前缀和的方式就可以对每个站点进行相应的标注了。

例如:

50 3
1 1 20
0 21 30
1 40 50

通过上面操作

a1=1,a21=-1

a21=100000-1=99999,a31=-100000

a40=1,a51=-1

然后求前缀和:

s1=a1+a0=1+0=1

s2=s1+a2=1+0=1

s3=s2+a3=1+0=1

.....

s20=1

s21=s20+a21=99999+1=100000

s22=s21+a22=100000+0=100000

.....

s30=s29+a30=100000+0=100000

s31=s30+a31=100000+(-100000)=0

s32=s31+a32=0+0=0

.....

s39=s38+a39=0+0=0

s40=s39+a40=0+1=1

s41=s40+a41=1+0=1

.....

s50=s49+a50=1+0=1

可以看出,[1,20]区间为1,所以坐地铁,[20,30]为100000,所以绕路,[31,39]区间为0,所以走路,[40,50]区间为1,所以坐地铁。符合题目要求。

但是要考虑 同一个站可能有完工的地铁和同时未完工的地铁输入数据。(这个模拟也是很重要的)

Code

#include<bits/stdc++.h>
using namespace std;
long long a[100001];
int main(){
	long long ans=0;
	memset(a,0,sizeof(a));
	long long dis,n;
	cin>>dis>>n;
	for(int i=1;i<=n;i++){
		int x,l,r;
		cin>>x>>l>>r;
		if(x==1){
			a[l]+=1;
			a[r+1]-=1;
		}else{
			a[l]+=100000;
			a[r+1]-=100000;
		}
	}
	for(int i=1;i<=dis;i++){
		a[i]=a[i-1]+a[i];
	}
	for(int i=1;i<=dis;i++){
		if(a[i]%100000!=0) ans+=3;
		else if(a[i]>=100000) ans+=30;
		else ans+=20;
	}
	cout<<ans;
	return 0;
}

注:代码没有额外再定义前缀和数组。

 另外只是想想为啥是if(a[i]%100000!=0),而不是if(a[i]<100000)

 [同一个站可能有完工的地铁和同时未完工的地铁输入数据。]

 例如:输入{1 1 10} {0 3 5}

a1=1+0=1

a11=0-1=-1

a3=100000

a6=-100000

前缀和:

s1=a1+a0=1+0=1

s2=1+0=1

s3=s2+a3=1+100000=100001

s4=s3+a4=100001

s5=s4+a5=100001

s6=s5+a6=100001+(-100000)=1

s7=s6+a7=1

s8=1

s9=1

s10=1

而根据题意有地铁的话就要坐地铁,所以[1,10]都要坐地铁,但是[3,5]都是100001,如果不对%100000的话,就得不到正确的结果了。

​​​​​​​

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值