[BZOJ 1576] 安全路径 最短路径树 路径压缩

题意

  给定一张 n 个点 m 条边的图, 保证对于任意的点 i , 从点 1 到点 i 的最短路唯一.

  对于任意的点 i , 询问: 将 1 到 i 的最短路中最后一条边删去之后, 从 1 到 i 的最短路 .

  n <= 100000, m <= 200000 .

分析

  首先跑 Dijsktra , 构建出最短路径树.

  接下来考虑每条非树边 E[p] = (u, v, d) 对答案的影响, 它能且仅能影响到点 u, v 之上, LCA(u, v) 之下的点的答案. (包括 u, v, 不包括 LCA(u, v)) .

  假装这个点是 x , 那么可以得到一条长度为 d[u] + d[v] + d - d[x] 的路径.

  设 val = d[u] + d[v] + d , 那么 ans[x] = min(val - d[x]) = min(val) - d[x] .

  现在的问题被抽象为: 对于一棵树, 若干次 [ 某条路径被某个权值覆盖 ] , 求每个点上的最小权值.

  我们仔细想一想, 发现直接先将所有非树边按照 val 从小到大排序, 然后用路径压缩均摊到 O(n) .

实现

  每个点只会被赋值一次, 只要能够快速跳到上一个没有赋值的点, 并进行赋值 .

  所以我想了想, 做法大致可以被描述为: 进行路径压缩的同时, 维护答案.

  Fill(x, y, val) , 返回 x 和 y 的 LCA , 或者 LCA 所在集合的代表元.

    当 x = y 时, 找到了 LCA 或者 LCA 所在集合的代表元, 直接返回 x .

    否则说明至少有一个点还没有到达 LCA , 深度大的那个点一定没有到达 LCA . 若 dep[x] < dep[y] , 交换 x, y .

    若点 x 还没有被染色, 那么染成 val .

    将 x 往上跳, 并维护路径压缩的结构, par[x] = Fill(par[x], y, val) .

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <cstdlib>
 4 #include <cctype>
 5 #include <algorithm>
 6 #include <queue>
 7 using namespace std;
 8 #define F(i, a, b) for (register int i = (a); i <= (b); i++)
 9 inline int rd(void) {
10     int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == ‘-‘) f = -1;
11     int x = 0; for (; isdigit(c); c = getchar()) x = x*10+c-‘0‘; return x*f;
12 }
13
14 const int N = 100005;
15 const int M = 400005;
16 const int INF = ~0u>>2;
17
18 #define fore(k, x) for (register int k = hd[x]; k > 0; k = nx[k])
19 int n, m, hd[N], nx[M], tot, adj[M], cost[M];
20 inline void Init(int x, int y, int d) { nx[++tot] = hd[x], hd[x] = tot, adj[tot] = y, cost[tot] = d; }
21
22 int d[N], dep[N], par[N]; priority_queue< pair<int, int> > q;
23 void Dij(void) {
24     fill(d+1, d+n+1, INF), q.push(make_pair(0, 1));
25     while (!q.empty()) {
26         pair<int, int> cur = q.top(); q.pop();
27         if (d[cur.second] != INF) continue;
28         d[cur.second] = -cur.first;
29         fore(k, cur.second)
30             if (d[adj[k]] == INF)
31                 q.push(make_pair(cur.first - cost[k], adj[k]));
32     }
33 }
34 void Prework(int x) {
35     fore(k, x) if (d[x] + cost[k] == d[adj[k]])
36         dep[adj[k]] = dep[x]+1, par[adj[k]] = x, Prework(adj[k]);
37 }
38
39 int idx, ans[N];
40 struct Edge {
41     int x, y, val;
42     friend inline bool operator < (Edge A, Edge B) { return A.val < B.val; }
43 }e[M];
44
45 inline int Find(int x, int y, int val) {
46     if (x == y) return x;
47     if (dep[x] < dep[y]) swap(x, y);
48     if (!ans[x]) ans[x] = val - d[x];
49     return par[x] = Find(par[x], y, val);
50 }
51
52 int main(void) {
53     #ifndef ONLINE_JUDGE
54         freopen("bzoj1576.in", "r", stdin);
55     #endif
56
57     n = rd(), m = rd();
58     F(i, 1, m) {
59         int a = rd(), b = rd(), t = rd();
60         Init(a, b, t), Init(b, a, t);
61     }
62
63     Dij();
64     Prework(1);
65
66     F(x, 1, n) fore(k, x)
67         if (d[x] + cost[k] != d[adj[k]] && d[adj[k]] + cost[k] != d[x] && x < adj[k])
68             e[++idx] = (Edge){x, adj[k], cost[k] + d[x] + d[adj[k]]};
69     sort(e+1, e+idx+1);
70
71     F(i, 1, idx)
72         Find(e[i].x, e[i].y, e[i].val);
73     F(i, 2, n) printf("%d\n", !ans[i] ? -1 : ans[i]);
74
75     return 0;
76 }
时间: 2024-12-16 06:04:39

[BZOJ 1576] 安全路径 最短路径树 路径压缩的相关文章

安全路径——最短路径树+dsu缩边

题目描述 思路 首先想到$dijkstra$跑完之后$build$一棵最短路径树.要找到每个节点i到根的满足要求的最短路,考虑把一些非树边加进去. 对于非树边$(u,v)$,因为节点i上方的边被占领,所以只能选择往下走,从非树边走到别的子树,设$u$属于$i$的子树,$v$不属于,那么$u,v$的$lca$经过$i$,且$i$经过$(u,v)$到根的最短路为$dist[u]+dist[v]-dist[i]+w(u,v)$,这样我们把每条非树边按照$dist[u]+dist[v]+w(u,v)$排

[usaco jan 09] 安全路径 travel [最短路径树]

题面: 传送门 思路: 既然最后一条边不能走,那么就一定是换了一条路,一条不经过这最后一条边的路 如果想要这条路最短,那么其在路上一定尽可能多地走了最短路径 因此,我们对这张图跑一遍从1开始的单源最短路,并建立出最短路径树 那么新的路径(1->u)一定是这样构成的:(1->v)+edge(v,w)+(w->u),其中w是u在最短路径树上的后代 那么,我们对于每一条非树边(u,v),其树上路径上所有点(除了lca)的答案,都可以被dis[u]+dis[v]+w(u,v)-dis[路径上的点

bzoj 4016 [FJOI2014]最短路径树问题(最短路径树+树分治)

4016: [FJOI2014]最短路径树问题 Time Limit: 5 Sec  Memory Limit: 512 MBSubmit: 426  Solved: 147[Submit][Status][Discuss] Description 给一个包含n个点,m条边的无向连通图.从顶点1出发,往其余所有点分别走一次并返回. 往某一个点走时,选择总长度最短的路径走.若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11,路 径B为1,3,2,11,路径B

BZOJ 2150 部落战争(最小路径覆盖)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2150 题意:一个n*m的国家,有些是障碍.对于一支军队,每次只能走C*R的格式(比如马是走1*2的格式),而且只能从上往下走.每个格子只能被一个军队经过.问最少需要多少军队可以遍历完所有格子? 思路:上下能连边的连边.最后就是最小路径覆盖. int a[N][N],n,m; char s[55][55]; int ID(int i,int j) { return (i-1)*m+j;

编程实现求一棵二叉树的最短树路径和最长树路径

Minimum Depth of Binary Tree Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. class Solution { public: int minDepth(TreeNode *root) { if(!r

# 最短Hamilton路径(二进制状态压缩)

最短Hamilton路径(二进制状态压缩) 题目描述:n个点的带权无向图,从0-n-1,求从起点0到终点n-1的最短Hamilton路径(Hamilton路径:从0-n-1不重不漏的每个点恰好进过一次) 题解:二进制状态压缩算法\(O(2^n*n^2)\),需要记录当前经过了哪些点,当前在哪个位置.\(f[i][j]\) ? \(i\)转化为二进制每一位代表是否经过该点,\(j\)表示当前位于j这个点 #include <iostream> #include <cstring> u

[51nod1443]路径和树

给定一幅无向带权连通图G = (V, E) (这里V是点集,E是边集).从点u开始的最短路径树是这样一幅图G1 = (V, E1),其中E1是E的子集,并且在G1中,u到所有其它点的最短路径与他在G中是一样的. 现在给定一幅无向带权连通图G和一个点u.你的任务是找出从u开始的最短路径树,并且这个树中所有边的权值之和要最小. Input 第一行有两个整数n和m(1 ≤ n ≤ 3*10^5, 0 ≤ m ≤ 3*10^5),表示点和边的数目. 接下来m行,每行包含3个整数 ui, vi, wi ,

51Nod 1443 路径和树

还是一道很简单的基础题,就是一个最短路径树的类型题目 我们首先可以发现这棵树必定满足从1出发到其它点的距离都是原图中的最短路 换句话说,这棵树上的每一条边都是原图从1出发到其它点的最短路上的边 那么直接跑最短路,SPFA,不存在的?我只信DJ,然后记录那些边在最短路上 然后直接跑MST即可.是不是很经典的水题 然后我又莫名拿了Rank1(没办法天生自带小常数) CODE #include<cstdio> #include<cctype> #include<cstring>

【51nod1443】路径和树(堆优化dijkstra乱搞)

点此看题面 大致题意:给你一个无向联通图,要求你求出这张图中从u开始的权值和最小的最短路径树的权值之和. 什么是最短路径树? 从\(u\)开始到任意点的最短路径与在原图中相比不变. 题解 既然要求最短路径,那么最容易想到的就是\(dijkstra\)和\(SPFA\)(毕竟Floyd的时间复杂度难以承受),又由于黄学长说能用\(dijkstra\)时尽量用\(dijkstra\),所以,我就打了一个堆优化的\(dijkstra\)开始乱搞. 其实,这道题目的思路真的挺简单的,只要朴素地做一遍\(