题目描述
最近小王所在的城市在修建地铁,已经有很多的地铁已经完工,但也有一些还在施工中。现在小王要出发去参加朋友的聚会,在出行时会尽可能的节省时间,地铁的速度非常快,假设每公里只需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的话,就得不到正确的结果了。