题目网址:
https://www.luogu.org/problemnew/show/P1081
题目描述
小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1 到 N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为Hi,城市 i 和城市 j 之间的距离 d[i,j]恰好是这两个城市海拔高度之差的绝对值,即d[i,j] = |Hi− Hj|。 旅行过程中,小 A 和小 B 轮流开车,第一天小 A 开车,之后每天轮换一次。他们计划选择一个城市 S 作为起点,一直向东行驶,并且最多行驶 X 公里就结束旅行。小 A 和小 B的驾驶风格不同,小 B 总是沿着前进方向选择一个最近的城市作为目的地,而小 A 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 X 公里,他们就会结束旅行。
在启程之前,小 A 想知道两个问题:
-
对于一个给定的 X=X0,从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
- 对任意给定的 X=Xi和出发城市 Si,小 A 开车行驶的路程总数以及小 B 行驶的路程
总数。
输入输出格式
输入格式:
第一行包含一个整数 N,表示城市的数目。
第二行有 N 个整数,每两个整数之间用一个空格隔开,依次表示城市 1 到城市 N 的海拔高度,即 H1,H2,……,Hn,且每个 Hi都是不同的。
第三行包含一个整数 X0。
第四行为一个整数 M,表示给定 M 组 Si和 Xi。
接下来的 M 行,每行包含 2 个整数 Si和 Xi,表示从城市 Si出发,最多行驶 Xi公里。
输出格式:
输出共 M+1 行。
第一行包含一个整数 S0,表示对于给定的 X0,从编号为 S0的城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小。
接下来的 M 行,每行包含 2 个整数,之间用一个空格隔开,依次表示在给定的 Si和
Xi下小 A 行驶的里程总数和小 B 行驶的里程总数。
输入输出样例
4 2 3 1 4 3 4 1 3 2 3 3 3 4 3
1 1 1 2 0 0 0 0 0
10 4 5 6 1 2 3 7 8 9 10 7 10 1 7 2 7 3 7 4 7 5 7 6 7 7 7 8 7 9 7 10 7
2 3 2 2 4 2 1 2 4 5 1 5 1 2 1 2 0 0 0 0 0
说明
【输入输出样例 1 说明】
各个城市的海拔高度以及两个城市间的距离如上图所示。
如果从城市 1 出发,可以到达的城市为 2,3,4,这几个城市与城市 1 的距离分别为 1,1,2,但是由于城市 3 的海拔高度低于城市 2,所以我们认为城市 3 离城市 1 最近,城市 2 离城市1 第二近,所以小 A 会走到城市 2。到达城市 2 后,前面可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,所以城市 4 离城市 2 最近,因此小 B 会走到城市 4。到达城市 4 后,前面已没有可到达的城市,所以旅行结束。
如果从城市 2 出发,可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,由于城市 3 离城市 2 第二近,所以小 A 会走到城市 3。到达城市 3 后,前面尚未旅行的城市为4,所以城市 4 离城市 3 最近,但是如果要到达城市 4,则总路程为 2+3=5>3,所以小 B 会直接在城市 3 结束旅行。
如果从城市 3 出发,可以到达的城市为 4,由于没有离城市 3 第二近的城市,因此旅行
还未开始就结束了。
如果从城市 4 出发,没有可以到达的城市,因此旅行还未开始就结束了。
【输入输出样例 2 说明】
当 X=7 时, 如果从城市 1 出发,则路线为 1 -> 2 -> 3 -> 8 -> 9,小 A 走的距离为 1+2=3,小 B 走的距离为 1+1=2。(在城市 1 时,距离小 A 最近的城市是 2 和 6,但是城市 2 的海拔更高,视为与城市 1 第二近的城市,所以小 A 最终选择城市 2;走到 9 后,小 A 只有城市 10 可以走,没有第 2 选择可以选,所以没法做出选择,结束旅行)
如果从城市 2 出发,则路线为 2 -> 6 -> 7 ,小 A 和小 B 走的距离分别为 2,4。
如果从城市 3 出发,则路线为 3 -> 8 -> 9,小 A 和小 B 走的距离分别为 2,1。
如果从城市 4 出发,则路线为 4 -> 6 -> 7,小 A 和小 B 走的距离分别为 2,4。
如果从城市 5 出发,则路线为 5 -> 7 -> 8 ,小 A 和小 B 走的距离分别为 5,1。
如果从城市 6 出发,则路线为 6 -> 8 -> 9,小 A 和小 B 走的距离分别为 5,1。
如果从城市 7 出发,则路线为 7 -> 9 -> 10,小 A 和小 B 走的距离分别为 2,1。
如果从城市 8 出发,则路线为 8 -> 10,小 A 和小 B 走的距离分别为 2,0。
如果从城市 9 出发,则路线为 9,小 A 和小 B 走的距离分别为 0,0(旅行一开始就结
束了)。
如果从城市10出发,则路线为 10,小A 和小B 走的距离分别为0,0。
从城市 2 或者城市 4 出发小 A 行驶的路程总数与小 B 行驶的路程总数的比值都最小,但是城市 2 的海拔更高,所以输出第一行为 2。
对于30%的数据,有1≤N≤20,1≤M≤20;
对于40%的数据,有1≤N≤100,1≤M≤100;
对于50%的数据,有1≤N≤100,1≤M≤1,000;
对于70%的数据,有1≤N≤1,000,1≤M≤10,000;
对于100%的数据,有1≤N≤100,000,1≤M≤100,000,-1,000,000,000≤Hi≤1,000,000,000,0≤X0≤1,000,000,000,1≤Si≤N,0≤Xi≤1,000,000,000,数据保证Hi 互不相同。
一点小心得
分析:这道题不会做,看别人题解看了一整天,算是有一点心得吧,分享一下,首先看数据,似乎可以暴力个部分分,每次选取一个点就开始模拟行走.每次都要寻找第一近和第二近,非常浪费时间,那么就预处理,怎么预处理呢?排序吗?有点复杂,离得最近的一定是高度差相近的,那么能排序并且在相近的元素中寻找的数据结构是什么呢?STL的set!每次找两个前驱和后继,然后排序取前两个.不过要注意,插入set要倒着插,因为每次都是去东边的城市.但是这样预处理之后还是不能通过所有的数据,我们来对算法仔细的分析,查找第一近和第二近的优化过了,枚举点似乎不能优化,那么只能优化模拟行走了.会发现两次行走可以合并成一次,有没有什么更快的枚举技巧呢?二分?似乎不行.那么就是倍增了.设f[i][j]为在位置i走2^j轮的位置,一轮就是A走和B走一次,fa[i][j]就是A在位置i走2^j轮的距离,fb[i][j]类同,那么怎么算呢?记住一个重要的性质:a^i-1 + a^i-1 = a^i,递推也可以这样,f[i][j] = f[f[i][j-1]][j-1],fa[i][j] = fa[i][j-1] + fa[f[i][j-1]][j-1],fb类同,因为n<=100000,所以2^i,i从19开始枚举,从大到小枚举,依次判断行不行即可.(真是复杂......)
对枚举一个常见的优化:倍增.
1.储存方式
1 struct sd{ 2 long long h,id; 3 }node[100005];//记录每个城市的高度和编号,因为等会儿要排序找前驱后继 4 long long pre[100005],next[100005],hh[100005],da[100005][33],db[100005][33],fi[100005],se[100005],f[100005][33]; 5 //f[i][j] 从i地走j轮,会到哪个地方 6 //da[i][j] 从i地走j轮,a要走多远 7 //db[i][j] 从i地走j轮,b要走多远 8 //fi(rst)[i] i地的最近点 9 //se(cond)[i] i地的第二近点 10 //pre[i] i的前驱 next[i] i的后继 形成链表 11 //hh[i] 记录高度
2.对每个城市进行预处理
PS.这一步中,查找最近城市与第二近的城市的代码较为冗杂,可以写得更优化,但蒟蒻的我码力不够,就凑合着看吧,望大佬勿喷。。。
1 void build(int v) 2 { 3 long long h[5]; 4 long long p1=pre[v]; 5 long long p2=pre[p1]; 6 long long n1=next[v]; 7 long long n2=next[n1]; 8 if(p1==0) h[1]=INF; else {h[1]=hh[v]-hh[p1];h[1]=h[1]>0?h[1]:-h[1];} 9 if(p2==0) h[2]=INF; else {h[2]=hh[v]-hh[p2];h[2]=h[2]>0?h[2]:-h[2];} 10 if(n1==0) h[3]=INF; else {h[3]=hh[v]-hh[n1];h[3]=h[3]>0?h[3]:-h[3];} 11 if(n2==0) h[4]=INF; else {h[4]=hh[v]-hh[n2];h[4]=h[4]>0?h[4]:-h[4];} 12 //算出距离 13 long long minn1=INF,minn2=INF,pp1=0,pp2=0; 14 if(minn1>h[1]) {minn1=h[1],pp1=p1;} 15 if(minn1>h[2]) {minn1=h[2],pp1=p2;} 16 if(minn1>h[3]) {minn1=h[3],pp1=n1;} 17 if(minn1>h[4]) {minn1=h[4],pp1=n2;} 18 //找出最近点 19 if(minn2>h[1]&&pp1!=p1||(minn2==h[1]&&hh[pp2]>hh[p1])) {minn2=h[1],pp2=p1;} 20 if(minn2>h[2]&&pp1!=p2||(minn2==h[2]&&hh[pp2]>hh[p2])) {minn2=h[2],pp2=p2;} 21 if(minn2>h[3]&&pp1!=n1||(minn2==h[3]&&hh[pp2]>hh[n1])) {minn2=h[3],pp2=n1;} 22 if(minn2>h[4]&&pp1!=n2||(minn2==h[4]&&hh[pp2]>hh[n2])) {minn2=h[4],pp2=n2;} 23 //找出第二近点 24 if(hh[pp1]>hh[pp2]&&minn1==minn2&&minn1!=INF) 25 {int gg=pp1;pp1=pp2;pp2=gg;} 26 if(minn1!=INF) 27 fi[v]=pp1; 28 if(minn2!=INF) 29 se[v]=pp2; 30 //删除链表上的点,因为是从东到西,所以先插入链表的西边的城市在后来不会被到达,所以删掉,以免出错 31 if(p1!=0) next[p1]=n1; 32 if(n1!=0) pre[n1]=p1; 33 }
3.预处理da[i][j] , db[i][j] , f[i][j]
这一步比较好理解,在这里就不赘述了
1 for(int i=1;i<=n;i++) 2 { 3 f[i][0]=fi[se[i]]; 4 if(i<=n-2) 5 da[i][0]=abs(hh[i]-hh[se[i]]); 6 if(i<=n-1) 7 db[i][0]=abs(hh[se[i]]-hh[fi[se[i]]]); 8 } 9 10 for (int i=1;i<=32;i++) 11 for (int j=1;j<=n;j++) 12 { 13 f[j][i]=f[f[j][i-1]][i-1]; 14 da[j][i]=da[j][i-1]+da[f[j][i-1]][i-1]; 15 db[j][i]=db[j][i-1]+db[f[j][i-1]][i-1]; 16 }
3.开始跑路,求解问题一
1 double minn=INF,minh=-1; 2 long long ans=1; 3 long long x; 4 scanf("%lld",&x); 5 for(int i=1;i<=n;i++) 6 { 7 long long l=x;//记录长度,确保在跑了x后停止 8 long long point=i;//跑路的指针 9 long long A=0,B=0;//分别记录A,B的跑路距离 10 for(int j=32;j>=0;j--) 11 { 12 if(f[point][j]!=0&&da[point][j]+db[point][j]<=l)//如果可以跑,那就跑呗 13 { 14 l-=da[point][j]+db[point][j]; 15 A+=da[point][j]; 16 B+=db[point][j]; 17 point=f[point][j]; 18 } 19 } 20 if (da[point][0]<=l)//最后再特判一下A还能不能走,如果可以,那就让他单独走一波 21 { 22 A+=da[point][0]; 23 l-=da[point][0]; 24 } 25 double C=INF; 26 if (B==0) 27 C=INF; 28 else 29 C=A*1.0/B; 30 //求比值 31 if (C<minn||C==minn&&hh[i]>minh) 32 { 33 minn=C; 34 minh=hh[i]; 35 ans=i; 36 } 37 } 38 if(minn!=INF) 39 printf("%lld ",ans); 40 else 41 printf("%lld ",node[1].id);
4.继续跑路,求解问题二
这个问题的写法,与上面极为相似,只是多个循环枚举每个起点就行了,代码不难想
1 long long m; 2 scanf("%lld",&m); 3 while(m--) 4 { 5 long long ss,xx; 6 scanf("%lld%lld",&ss,&xx); 7 long long l=xx; 8 long long point=ss; 9 long long A=0,B=0; 10 for(int j=32;j>=0;j--) 11 { 12 if(f[point][j]!=0&&da[point][j]+db[point][j]<=l) 13 { 14 l-=da[point][j]+db[point][j]; 15 A+=da[point][j]; 16 B+=db[point][j]; 17 point=f[point][j]; 18 } 19 } 20 if (da[point][0]<=l) 21 { 22 A+=da[point][0]; 23 l-=da[point][0]; 24 } 25 printf("%lld %lld ",A,B); 26 }
最后问题就解决了
这道题其实很有研究的价值,也算是倍增的一道经典题型了
倍增再一次体现了log线性处理的优越性,可以用于今后时间复杂度较迷的题目上。。。