[关闭]
@Gary-Ying 2018-08-14T13:01:18.000000Z 字数 3208 阅读 1020

[NOIP2013][Luo1967]货车运输 解题报告

解题报告


题目传送门(luogu最后一个数据请忽略)

题目大意:给定一个n个点m条边无向图(有重边,无自环),求q对点之间最小边最大的路径

看到题目很容易想到二分+并查集,这样时间复杂度是很明显会超时,那怎么办呢?

尝试求原图的最大生成树,我们可以证明:符合条件的路径一定在最大生成树上(下文的生成树指最大生成树)。证明如下:
不妨假设我们要求点u到点v之间的最大的最小边,如果这条最小边不在生成树上,那么生成树上一定有一条比它大的边连接两个联通快,所以,这条最小边一定在生成树上。

所以问题就转化成了求树上两点间路径上的最小值,有大佬就会跳出来说树剖但是众所周知,树剖难写,我不会QAQ。可以有更简单的方法,比如说树上倍增,时间复杂度(单次查询)仅为

简单介绍一下树上倍增。树上倍增就是利用倍增的思想维护树上的信息。举个栗子,树上倍增最基础的数组就是,表示从这个点向上跳步能到达的点的编号,我们可以很方便地预处理出

在树上倍增的同时,我们可以维护很多信息,例如和,最值,GCD等等,这道题需要利用最小值,所以可以用表示从这个点向上跳步经过的边的最小值,我们可以很方便地预处理出

维护出了这些信息后,我们就可以很方便地求出树上两点路径上的最小值了,具体来说,就是先求出u到LCA的最小值,再求出v到LCA的最小值,把它们合并即可。

预处理:

  1. for (int j = 1; j <= 14; ++j)
  2. for (int i = 1; i <= n; ++i){
  3. fa[i][j] = fa[ fa[i][j-1] ][j-1];
  4. Min[i][j] = min(Min[i][j-1] , Min[ fa[i][j-1] ][j-1]);
  5. }

求两点路径上最小值

  1. int Dis(int u, int v){
  2. //printf("Dis %d %d ", u, v);
  3. int L = LCA(u,v); int res = 1000000000;
  4. int lenu = dep[u] - dep[L];
  5. for (int j = 0; j <= 14; ++j) if (lenu >> j & 1) res = min(res, Min[u][j]), u = fa[u][j];
  6. int lenv = dep[v] - dep[L];
  7. for (int j = 0; j <= 14; ++j) if (lenv >> j & 1) res = min(Min[v][j], res), v = fa[v][j];
  8. //printf("%d\n", res);
  9. return res;
  10. }

Code

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. using namespace std;
  5. const int maxm = 50007;
  6. const int maxn = 10007;
  7. int n, m;
  8. struct Edge{
  9. int u, v, cost;
  10. } a[maxm];
  11. bool cmp(Edge a, Edge b){
  12. return a.cost > b.cost;
  13. }
  14. //EdgeTable
  15. int edgenum, head[maxn], Next[maxn << 1], vet[maxn << 1], val[maxn << 1];
  16. inline void addedge(int u, int v, int cost){
  17. ++edgenum;
  18. vet[edgenum] = v;
  19. val[edgenum] = cost;
  20. Next[edgenum] = head[u];
  21. head[u] = edgenum;
  22. }
  23. //Union-Set
  24. int Fa[maxn];
  25. int Find(int x){return Fa[x] = Fa[x]==x?Fa[x]:Find(Fa[x]);}
  26. inline void Union(int u, int v){
  27. int F1 = Find(u), F2 = Find(v);
  28. if (F1 != F2) Fa[F2] = F1;
  29. }
  30. void Union_Set_init(){
  31. for (int i = 1; i <= n; ++i)
  32. Fa[i] = i;
  33. }
  34. int fa[maxn][20], Min[maxn][20], dep[maxn];
  35. void DFS(int u, int pre, int d){
  36. fa[u][0] = pre; dep[u] = d;
  37. for (int e = head[u]; e; e = Next[e]){
  38. int v = vet[e], cost = val[e];
  39. if (v != pre){
  40. Min[v][0] = cost;
  41. //printf("v = %d, cost = %d, %d\n",v, cost, Min[v][0]);
  42. DFS(v, u, d + 1);
  43. }
  44. }
  45. }
  46. int LCA(int u, int v){ //倍增求LCA
  47. if (dep[u] < dep[v]) swap(u, v);
  48. int len = dep[u] - dep[v];
  49. for (int j = 0; j <= 14; ++j) if (len >> j & 1) u = fa[u][j];
  50. for (int j = 14; j >= 0; --j) if (fa[u][j] != fa[v][j]) u = fa[u][j], v = fa[v][j];
  51. //printf("LCA %d %d %d\n", u, v, u==v?u:fa[u][0]);
  52. if (u==v) return u; return fa[u][0];
  53. }
  54. int Dis(int u, int v){ //求两点路径上最小值
  55. //printf("Dis %d %d ", u, v);
  56. int L = LCA(u,v); int res = 1000000000;
  57. int lenu = dep[u] - dep[L];
  58. for (int j = 0; j <= 14; ++j) if (lenu >> j & 1) res = min(res, Min[u][j]), u = fa[u][j];
  59. int lenv = dep[v] - dep[L];
  60. for (int j = 0; j <= 14; ++j) if (lenv >> j & 1) res = min(Min[v][j], res), v = fa[v][j];
  61. //printf("%d\n", res);
  62. return res;
  63. }
  64. int main(){
  65. scanf("%d%d", &n, &m);
  66. for (int i = 1; i <= m; ++i){
  67. scanf("%d%d%d", &a[i].u, &a[i].v, &a[i].cost);
  68. }
  69. sort(a + 1, a + 1 + m, cmp);
  70. Union_Set_init();
  71. for (int i = 1; i <= m; ++i){
  72. int tu = a[i].u, tv = a[i].v, cost = a[i].cost;
  73. if (Find(tu) != Find(tv)){
  74. Union(tu, tv);
  75. addedge(tu, tv, cost);
  76. addedge(tv, tu, cost);
  77. }
  78. }
  79. for (int i = 1; i <= n; ++i)
  80. dep[i] = 1;
  81. DFS(1, 0, 1);
  82. for (int j = 1; j <= 14; ++j)
  83. for (int i = 1; i <= n; ++i){
  84. fa[i][j] = fa[ fa[i][j-1] ][j-1];
  85. Min[i][j] = min(Min[i][j-1] , Min[ fa[i][j-1] ][j-1]);
  86. }
  87. int Q; scanf("%d", &Q);
  88. while (Q--){
  89. int u, v; scanf("%d%d", &u, &v);
  90. if (Find(u) == Find(v))printf("%d\n", Dis(u,v));
  91. else printf("-1\n");
  92. }
  93. /*for (int i = 1; i <= n; ++i){
  94. for (int j = 0; j <= 2; ++j)
  95. printf("%d ", Min[i][j]);
  96. printf("\n");
  97. }*/
  98. return 0;
  99. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注