~~~题面~~~

题解:

吐槽:找了好久的错,换了n种方法,重构一次代码,,,,

最后发现,,,

数组开小了,其实一开始尝试开大了数组,但唯独没有尝试开大手写队列的数组。。。。

思路:

有两种方法,这里都介绍一下吧,分别在时间复杂度和代码复杂度上各有优势。

第一种:时间复杂度更优,代码复杂

观察到转弯时需要多消耗1的费用,不转弯则不用。因此我们记录一个last表示这个点的最短路是从哪走来的。(其实就是记录路径)

然后注意到A ---> C 与A ---> B ---> C是等效的,因此我们可以直接向最近的转折点连边。

跑最短路即可。

洛谷题解里面有一篇跟这个思路基本相同,但有些细节没有注意到,于是我发现了一组数据可以hack掉这篇题解。。。

这种方法细节很多,下面来总结一下一些可能会陷入的误区(要注意的细节):

1,重复元素的处理。

  有两种选择:去重 or 特判;

  由于去重无论在时间复杂度还是代码复杂度上都占劣势,这里选择特判,方法就是在spfa判断是否需要转折的时候加一句x 和 now必须不同就可以了

2,last的统计

  last统计的应是当前找到的最短路径上节点u的上一个节点,这样就可以判断转折了。

  但我们注意到有这么一种情况,最短路可能有2条,而因为一个点只会向最近的两个点连边,所以一旦最短路超过一条,就代表当前找到的最小权值可以从两种方向不同的方案得到。

  也就意味了无论去往哪个方向,都有无需转折的方案。所以一旦我们找到一条长度与当前路径相同的路径,且转移点不与被转移点相同(x != now),那么我们可以判定这个点不需要转折的费用。

  但这样就够了吗?这也是很容易陷入的一个误区。

  因为可能有这样一种情况:

  出现了这么一个点,到达它的最短路径有2条,即符合无需转折的条件,但是找到这两条最短路的时间不同,在找到第一条最短路并将其入队后,在找到第二条最短路前,

  它已经成功出队并且更新了一个必经节点(即正确答案所需节点),但由于需要转折,它给到达这个必经节点的路径长增加了1,但是实际上这个点是无需转折的,

  所以它给这个必经节点新增的1就是不需要的,于是我们就得到了一个比正确答案大1的答案(如果这种情况多次出现则可能不止相差1),这也是我认为的上面那篇题解会被hack的原因。

  那么如何解决这个问题呢?

  其实很简单,我们观察到之所以会出现这样的情况,是因为只有dis[x]被更新时才会将x加入队列以更新其他点,这在普通的spfa中当然是正确的,因为dis[x]不被更新,x当然无法找到更短的路来更新别人,

  但这里是不同的,因为它多了一个是否转折的影响,因此当这个条件被改变时,我们也应将其加入队列,因为它现在又有可能更新别人了。所以我们在找到第二条路径时也将其加入队列即可。

3,点编号的记录问题

  当我码到一半的时候,,,发现直接用行列来计算的编号由于n很大,将会变得非常大,这时用数组肯定是存不下的,那怎么办呢?用map?

  其实不用,我们可以直接新建结构体,在记录一个节点的位置信息时,顺便记录id,然后在之后用到这个点的过程中,都直接用结构体储存相关信息(包括链式前向星)。

  于是我们就可以很方便的得知一个点的id了

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define R register int
  4. #define getchar() *o++
  5. #define AC 100100
  6. #define ac 502000
  7. #define inf 2139062143
  8. char READ[], *o = READ;
  9. /*因为如果没有换乘站的话,是无法改变路线的,因此在没有换乘的情况下,
  10. 如果不能一次性到达家,那就是进入了死胡同,,,,
  11. 所以一开始先判断一下,如果不能一下到达家,那就必然要经过中转站,
  12. 将同行同列且最近的中转站连边,因为中转只需要一秒,所以可以直接记录一个last,
  13. 记录最近的路径是从哪里转移的,如果可以从两边转移,那么last记为0,
  14. 因为要用到last[x]的情况只有用x更新别人一种,所以不用初始化last,在x更新别人之前它肯定会被别人更新,
  15. 所以更新时判断,如果需要转弯,那么时间+1,否则就是板子。
  16. 当然这样合法是建立在中转只需要一秒的情况下的,不然就要记录2个方向的情况了,
  17. 但是n可以到20000, 这样的话编号不够用???用map???
  18. 没事,,,直接存id就好了,不过跳着连边是无意义的,所以只能连最近的边*/
  19. int n, m, cnt;
  20. int dis[AC];
  21. int Next[ac], length[ac], Head[AC], tot;
  22. int head, tail;
  23. bool z[AC];
  24. struct node{
  25. int x, y, id;//直接把id和node绑在一起,就可以不用map了?
  26. }ss, tt, s[AC], q[ac], last[AC], date[ac];//error!!!队列啊啊啊啊啊啊
  27.  
  28. inline int read()
  29. {
  30. int x = ; char c = getchar();
  31. while(c > '' || c < '') c = getchar();
  32. while(c >= '' && c <= '') x = x * + c -'', c = getchar();
  33. return x;
  34. }
  35.  
  36. bool operator == (node a, node b)
  37. {
  38. if(a.x == b.x && a.y == b.y) return true;
  39. else return false;
  40. }
  41.  
  42. inline bool cmp(node a, node b)
  43. {
  44. if(a.x != b.x) return a.x < b.x;
  45. else return a.y < b.y;
  46. }
  47.  
  48. inline bool cmp1(node a, node b)
  49. {
  50. if(a.y != b.y) return a.y < b.y;
  51. else return a.x < b.x;
  52. }
  53.  
  54. inline void add(node f, node w, int S)
  55. {
  56. date[++tot] = w, Next[tot] = Head[f.id], length[tot]= S, Head[f.id] = tot;
  57. date[++tot] = f, Next[tot] = Head[w.id], length[tot]= S, Head[w.id] = tot;
  58. //printf("(%d, %d) ---> (%d, %d) : %d\n", f.x, f.y, w.x, w.y, S);
  59. }
  60.  
  61. void pre()
  62. {
  63. n = read(), m = read();
  64. cnt = m;
  65. for(R i = ; i <= m; i++)
  66. s[i].x = read(), s[i].y = read();
  67. ss.x = read(), ss.y = read(), tt.x = read(), tt.y = read();
  68. /*for(R i=1;i<=m;i++)//去重(可能和ss or tt重复)
  69. if(s[i] == ss || s[i] == tt)//还是直接就在这里处理干净吧,后面处理太麻烦
  70. {
  71. for(R j=i;j<cnt;j++) s[j] = s[j + 1];//类似与插排
  72. --cnt;
  73. }*///在spfa中加入判断之后就不用去重了
  74. ss.id = cnt + , tt.id = cnt + ;
  75. if(ss.x == tt.x)
  76. {
  77. printf("%d\n",abs(ss.y - tt.y) * );
  78. exit();
  79. }
  80. else if(ss.y == tt.y)
  81. {
  82. printf("%d\n",abs(ss.x - tt.x) * );
  83. exit();
  84. }
  85. memset(dis, , sizeof(dis));
  86. }
  87.  
  88. void spfa()
  89. {
  90. node x, now; int go;
  91. q[++tail] = ss, dis[ss.id] = , z[ss.id] = true;
  92. while(head < tail)
  93. {
  94. x = q[++head];
  95. z[x.id] = false;
  96. for(R i = Head[x.id]; i ; i = Next[i])
  97. {
  98. now = date[i];
  99. go = dis[x.id] + length[i];
  100. if(last[x.id].id && (x.x != now.x || x.y != now.y))//如果需要中转则时间+1,error要特别注意重复元素的处理,,,,重复元素可以看错距离为0的中转。。。
  101. if((x.x == now.x && last[x.id].x != x.x) || (x.y == now.y && last[x.id].y != x.y)) ++go;
  102. if(dis[now.id] > go)
  103. {
  104. last[now.id] = x;//记录点
  105. dis[now.id] = go;
  106. if(!z[now.id])//error!!!只有没有进队列的才加入队列,不然会导致last统计错误
  107. {//因为last统计的正确性正是基于如果一个点x被last[x]更新,那么下次last[x]更新它必然是因为找到了更优解(不然last[x]不会入队)
  108. z[now.id] = true;//但没有这个判断就会导致没有找到更优解却还是二次进入,那么重复进入就会导致下方的else判断错误
  109. q[++tail] = now;//(因为可能被2次更新,但一直没有轮到这个点)
  110. }
  111. }
  112. else if(dis[now.id] == go && x.x != last[now.id].x && x.y != last[now.id].y)
  113. {
  114. last[now.id].id = ;//如果相等的话则需要判断
  115. if(!z[now.id])//error!!!相等也需要加入队列,因为本来可以双向到达而省去中转费的站,可能因为加入队列时机不对而错过
  116. {
  117. z[now.id] = true;
  118. q[++tail] = now;
  119. }
  120. }
  121. }
  122. }
  123. if(dis[tt.id] != inf) printf("%d\n",dis[tt.id]);
  124. else printf("-1\n");
  125. }
  126.  
  127. void build()
  128. {//所以上放那种建图是错误的,,,,,,,特判ss和tt反而会错过一些东西
  129. for(R i = ; i <= cnt; i++) s[i].id = i;//定好编号
  130. s[++cnt] = ss, s[++cnt] = tt;//直接将这两个点加进来岂不是更好,,,,
  131. sort(s + , s + cnt + , cmp);//先按x排序(注意上方的加入s和t要放在确定编号后,因为这两个点的编号是之前就确定了的)
  132. for(R i = ; i <= cnt; i++)//因为连双向边,所以只要判断后面就可以了
  133. if(s[i].x == s[i+].x) add(s[i], s[i+], (s[i+].y - s[i].y) * );
  134. sort(s + , s + cnt + , cmp1);//再按y排一次
  135. for(R i = ; i <= cnt; i++)
  136. if(s[i].y == s[i+].y) add(s[i], s[i+], (s[i+].x - s[i].x) * );
  137. }
  138.  
  139. int main()
  140. {
  141. // freopen("in.in","r",stdin);
  142. fread(READ, , , stdin);
  143. pre();
  144. build();
  145. spfa();
  146. //fclose(stdin);
  147. return ;
  148. }

第二种:时间复杂度与空间复杂度稍大,但实现简单,细节很少,思路易懂

1,建图方法:

  对于一个点,我们将与它同行or同列的所有点连边,边权为距离*2(题目要求) + 1(强制转折)

2,为什么可以这样连边呢?

  因为可以观察到一个转折点如果不转折,那么实际上它是没有任何意义的,因此我们可以当做没有经过它,在图上表现为跳过它直接向那个要转折的点连边,

  由于不知道在哪个点转折,所以只要是同行or同列,每个点都要连边。

3,最后直接跑最短路就可以了,注意一下因为终点也被强制转折了,所以我们输出的时候答案要-1.

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define R register int
  4. #define getchar() *o++
  5. #define AC 100100
  6. #define ac 1002000
  7. #define inf 2139062143
  8. char READ[], *o = READ;
  9. /*因为如果没有换乘站的话,是无法改变路线的,因此在没有换乘的情况下,
  10. 如果不能一次性到达家,那就是进入了死胡同,,,,
  11. 所以一开始先判断一下,如果不能一下到达家,那就必然要经过中转站,
  12. 将同行同列且最近的中转站连边,因为中转只需要一秒,所以可以直接记录一个last,
  13. 记录最近的路径是从哪里转移的,如果可以从两边转移,那么last记为0,
  14. 因为要用到last[x]的情况只有用x更新别人一种,所以不用初始化last,在x更新别人之前它肯定会被别人更新,
  15. 所以更新时判断,如果需要转弯,那么时间+1,否则就是板子。
  16. 当然这样合法是建立在中转只需要一秒的情况下的,不然就要记录2个方向的情况了,
  17. 但是n可以到20000, 这样的话编号不够用???用map???
  18. 没事,,,直接存id就好了,不过跳着连边是无意义的,所以只能连最近的边*/
  19. int n, m, cnt;
  20. int dis[AC];
  21. int Next[ac], length[ac], Head[AC], tot;
  22. int head, tail;
  23. bool z[AC];
  24. struct node{
  25. int x, y, id;//直接把id和node绑在一起,就可以不用map了?
  26. }ss, tt, s[AC], q[ac], last[AC], date[ac];
  27.  
  28. inline int read()
  29. {
  30. int x = ; char c = getchar();
  31. while(c > '' || c < '') c = getchar();
  32. while(c >= '' && c <= '') x = x * + c -'', c = getchar();
  33. return x;
  34. }
  35.  
  36. bool operator == (node a, node b)
  37. {
  38. if(a.x == b.x && a.y == b.y) return true;
  39. else return false;
  40. }
  41.  
  42. inline bool cmp(node a, node b)
  43. {
  44. if(a.x != b.x) return a.x < b.x;
  45. else return a.y < b.y;
  46. }
  47.  
  48. inline bool cmp1(node a, node b)
  49. {
  50. if(a.y != b.y) return a.y < b.y;
  51. else return a.x < b.x;
  52. }
  53.  
  54. inline void add(node f, node w, int S)
  55. {
  56. date[++tot] = w, Next[tot] = Head[f.id], length[tot]= S, Head[f.id] = tot;
  57. date[++tot] = f, Next[tot] = Head[w.id], length[tot]= S, Head[w.id] = tot;
  58. //printf("(%d, %d) ---> (%d, %d) : %d\n", f.x, f.y, w.x, w.y, S);
  59. }
  60.  
  61. void pre()
  62. {
  63. n = read(), m = read();
  64. cnt = m;
  65. for(R i = ; i <= m; i++)
  66. s[i].x = read(), s[i].y = read();
  67. ss.x = read(), ss.y = read(), tt.x = read(), tt.y = read();
  68. /*for(R i=1;i<=m;i++)//去重(可能和ss or tt重复)
  69. if(s[i] == ss || s[i] == tt)//还是直接就在这里处理干净吧,后面处理太麻烦
  70. {
  71. for(R j=i;j<cnt;j++) s[j] = s[j + 1];//类似与插排
  72. --cnt;
  73. }*///因为添加了去重的步骤,所以这里的去重也变得不必要了
  74. //在新的建图方式下,,,,,可以直接暴力跑,相当于在枚举那个点作为转折点了
  75. ss.id = cnt + , tt.id = cnt + ;
  76. if(ss.x == tt.x)
  77. {
  78. printf("%d\n",abs(ss.y - tt.y) * );
  79. exit();
  80. }
  81. else if(ss.y == tt.y)
  82. {
  83. printf("%d\n",abs(ss.x - tt.x) * );
  84. exit();
  85. }
  86. memset(dis, , sizeof(dis));
  87. }
  88.  
  89. void spfa()
  90. {
  91. node x, now; int go;
  92. q[++tail] = ss, dis[ss.id] = , z[ss.id] = true;
  93. while(head < tail)
  94. {
  95. x = q[++head];
  96. z[x.id] = false;
  97. for(R i = Head[x.id]; i ; i = Next[i])
  98. {
  99. now = date[i];
  100. go = dis[x.id] + length[i];
  101. if(dis[now.id] > go)
  102. {
  103. last[now.id] = x;//记录点
  104. dis[now.id] = go;
  105. if(!z[now.id])//error!!!只有没有进队列的才加入队列,不然会导致last统计错误
  106. {//因为last统计的正确性正是基于如果一个点x被last[x]更新,那么下次last[x]更新它必然是因为找到了更优解(不然last[x]不会入队)
  107. z[now.id] = true;//但没有这个判断就会导致没有找到更优解却还是二次进入,那么重复进入就会导致下方的else判断错误
  108. q[++tail] = now;//(因为可能被2次更新,但一直没有轮到这个点)
  109. }
  110. }
  111. }
  112. }
  113. if(dis[tt.id] != inf) printf("%d\n",dis[tt.id] - );//这里要-1,因为把这里当中转站的时候在这里也强制转折了一次
  114. else printf("-1\n");
  115. }
  116. //可能我需要更加暴力的做法,,,
  117. //不再向最近的连边,而是向所有同列,同行的都连边。
  118. //因为一个转折点如果不转折的话,那就是无效的,于是在这种方法中它体现为,
  119. //直接跳过了这些点。连到了转折的那个点。
  120. //也就是说强制每个转折点都转折,而不转折的转折点就当做没有经过
  121. //这样虽然可能会多建很多边,但是可以保证正确性, 也不用在额外判断是否是转折点了
  122. //代码复杂度--。。。。。。
  123. void build()
  124. {//所以上放那种建图是错误的,,,,,,,特判ss和tt反而会错过一些东西
  125. for(R i = ; i <= cnt; i++) s[i].id = i;//定好编号
  126. s[++cnt] = ss, s[++cnt] = tt;//直接将这两个点加进来岂不是更好,,,,
  127. sort(s + , s + cnt + , cmp);//先按x排序(注意上方的加入s和t要放在确定编号后,因为这两个点的编号是之前就确定了的)
  128. for(R i = ; i <= cnt; i++)//因为连双向边,所以只要判断后面就可以了
  129. {
  130. int l = i + ;
  131. while(s[i].x == s[l].x)
  132. {
  133. add(s[i], s[l], (s[l].y - s[i].y) * + );
  134. ++l;
  135. }
  136. }
  137. sort(s + , s + cnt + , cmp1);//再按y排一次
  138. for(R i = ; i <= cnt; i++)
  139. {
  140. int l = i + ;
  141. while(s[i].y == s[l].y)
  142. {
  143. add(s[i], s[l], (s[l].x - s[i].x) * + );
  144. ++l;
  145. }
  146. }
  147. }
  148.  
  149. int main()
  150. {
  151. // freopen("in.in","r",stdin);
  152. fread(READ, , , stdin);
  153. pre();
  154. build();
  155. spfa();
  156. //fclose(stdin);
  157. return ;
  158. }

[SHOI2012]回家的路 最短路的更多相关文章

  1. P3831 [SHOI2012]回家的路

    P3831 [SHOI2012]回家的路 分层图基础题,就是建图稍有麻烦   #include<cstdio> #include<algorithm> #include< ...

  2. [SHOI2012]回家的路

    题目背景 SHOI2012 D2T1 题目描述 2046 年 OI 城的城市轨道交通建设终于全部竣工,由于前期规划周密,建成后的轨道交通网络由2n2n条地铁线路构成,组成了一个nn纵nn横的交通网.如 ...

  3. BZOJ.2834.回家的路(最短路Dijkstra 拆点)

    题目链接 对于相邻的.处在同在一行或一列的车站连边,然后用dis[x][0/1](或者拆点)分别表示之前是从横边还是竖边到x的,跑最短路. 我选择拆点.. //13028kb 604ms #inclu ...

  4. 题解 P3831 [SHOI2012]回家的路

    什么叫分层图最短路,我不会/kk 感觉自己做法和其他题解不大一样所以过来发篇题解了. 未刻意卡常拿下最优解 题目大意 就是说给你一个 \(n \times n\) 的网格图和 \(m\) 个可换乘点, ...

  5. Bzoj 2834: 回家的路 dijkstra,堆优化,分层图,最短路

    2834: 回家的路 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 62  Solved: 38[Submit][Status][Discuss] D ...

  6. 分层图最短路【bzoj2834】: 回家的路

    分层图最短路[bzoj2834]: 回家的路 题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2834 这道题难在建边. 自己写的时候想到了 ...

  7. bzoj 2834: 回家的路

    题目 F.A.Qs Home Discuss ProblemSet Status Ranklist Contest 入门OJ ModifyUser  DCOI Logout 捐赠本站 Notice:1 ...

  8. 【bzoj2834】回家的路 分层图最短路

    题目描述 输入 输出 样例输入 2 1 1 2 1 1 2 2 样例输出 5 题解 分层图最短路 dis[i][0]表示到i为横向时起点到i的最短路,dis[i][1]表示到i为纵向时起点到i的最短路 ...

  9. 洛谷P3831 回家的路

    题目背景 SHOI2012 D2T1 题目描述 \(2046\) 年 \(OI\) 城的城市轨道交通建设终于全部竣工,由于前期规划周密,建成后的轨道交通网络由\(2n\)条地铁线路构成,组成了一个\( ...

随机推荐

  1. Android7.0 应用内升级

    Android7.0应用内升级 最近线上项目在7.0机器上出现应用内升级失败,原来是由于Android7.0权限问题导致. 如果项目的 targetSdkVersion>=24 在处理应用内升级 ...

  2. MySQL不能连接本地数据库10061

    可能的原因是本地服务器没有启动,在安装配置MySQL时,我去掉了开机自动开启,所以开机之后出现了错误10061 解决办法: 一.计算机右击选择管理 二.选择服务,找到MySQL,右击手动,选择启动服务

  3. Clojure基础课程2-Clojure中的数据长啥样?

    本文来自网易云社区 作者:李诺 " Clojure is elegant and pragmatic; it helps me focus more on solving business ...

  4. 浅析Win8/8.1下安装SQL Server 2005 出现服务项无法正常启动解决方案

    如何才能在微软最新的Windows8/Windows 8.1下正常使用SQL Server 2005套件呢?下面就简单介绍利用文件替换法,解决其服务项无法正常启动的临时方案.当然还是建议使用SQL S ...

  5. Python拼接字符串的7种方法

    1.直接通过+操作: s = 'Python'+','+'你好'+'!'print(s) 打印结果: Python,你好! 2.通过join()方法拼接: 将列表转换成字符串 strlist=['Py ...

  6. MySQL☞in语句

    in语句: 1)列名 in(数值1,数值2,数值3…):求出满足该列的多个列值 格式: select 列名1,列名2  from 表名 where 列名 in (数值1,数值2,数值3...) 如下图 ...

  7. 第六模块:WEB框架开发 第1章·Django框架开发88~128

    88-Ajax简介 89-Ajax的简单实现 90-基于Ajax的传递数据 91-基于Ajax的登录验证 92-基于Form表单的文件上传 93-请求头之contentType 94-Ajax传递js ...

  8. lesson 18 Electric currents in modern art

    lesson18 Electric currents in modern art electricity n. 电力:电流; electric adj. 电的:电动的; electronic adj. ...

  9. 孤荷凌寒自学python第八十天开始写Python的第一个爬虫10

    孤荷凌寒自学python第八十天开始写Python的第一个爬虫10 (完整学习过程屏幕记录视频地址在文末) 原计划今天应当可以解决读取所有页的目录并转而取出所有新闻的功能,不过由于学习时间不够,只是进 ...

  10. Machine Learning笔记整理 ------ (五)决策树、随机森林

    1. 决策树 一般的,一棵决策树包含一个根结点.若干内部结点和若干叶子结点,叶子节点对应决策结果,其他每个结点对应一个属性测试,每个结点包含的样本集合根据属性测试结果被划分到子结点中,而根结点包含样本 ...