题解 P2483 【【模板】k短路([SDOI2010]魔法猪学院)】

Cyhlnj

2018-09-19 17:46:47

Solution

[俞鼎力大牛的课件](http://www.docin.com/p-1387370338.html) 对于原图以 $t$ 为根建出任意一棵最短路径树 $T$,即反着从 $t$ 跑出到所有点的最短路 $dis$ 它有一些性质: **性质1:** 对于一条 $s$ 到 $t$ 的路径的边集 $P$,去掉 $P$ 中和 $T$ 的交集,记为 $P'$。 那么 $P'$ 对于中任意相邻(从 $s$ 到 $t$ 的顺序)的两条边 $e,f$,满足 $f$ 的起点在 $T$ 中为 $e$ 的终点的祖先或者为相同点。 因为 $P$ 中 $e,f$ 之间由树边相连或者直接相连。 **性质2:** 对于不在 $T$ 中的边 $e$ ,设 $u$ 为起点,$v$ 为终点,$w$为权值。 定义 $\Delta_e=dis_v+w-dis_u$,即选这条边的路径和最短路的长度的差 设 $L_P$ 表示路径长度,则有 $$L_P=dis_s+\sum_{e\in p'}\Delta_e$$ 这很显然。 **性质3:** 对于满足性质 $1$ 的 $P'$的定义的边集 $S$,有且仅有一条 $s$ 到 $t$ 的路径的边集 $P$,使得 $P'=S$。 因为树 $T$ 上的两个点之间有且仅有一条路径。 **问题转化** 求第 $k$ 小的满足性质 $1$ 的 $P'$的定义的边集 **算法** 用小根堆维护边集 $P$ 初始 $P$ 为空集(实际上只要维护边集当前尾部的边的起点是哪一个就好了,空集即 $s$) 每次取出最小权值的边集 $P$,设当前尾部的边的起点为 $x$ 有两种方法可以得到一个新的边集: **1.**替换 $x$ 为起点的这条边为一条刚好大于等于它的非树边。 **2.**尾部接上一条起点为以 $x$ 为起点的这条边的终点在 $T$ 中祖先(包括自己)连出去的所有非树边的最小边。 然后就是怎么维护祖先出去的所有非树边的最小边: 显然可以从祖先转移过来,直接可并堆即可。 又因为要保留每个点的信息,所以合并的时候可持久化即可 和线段树合并的可持久化一样,然后就可以过了。 建议可以看一看课件 ```cpp # include <bits/stdc++.h> using namespace std; typedef long long ll; template <class Num> inline void Cmax(Num &x, const Num y) { x = y > x ? y : x; } template <class Num> inline void Cmin(Num &x, const Num y) { x = y < x ? y : x; } const int maxn(5005); const int maxm(2e5 + 5); const double eps(1e-8); int n, m, first[maxn], cnt, vis[maxn], rt[maxn], tot, cov[maxm << 1], ans, fa[maxn]; double se, e, dis[maxn]; priority_queue < pair <double, int> > q; struct Heap { int ls, rs, dis, ed; double w; } tr[maxm * 20]; struct Edge { int to, next; double w; } edge[maxm << 1]; inline void Add(int u, int v, double w) { edge[cnt] = (Edge){v, first[u], w}, first[u] = cnt++; edge[cnt] = (Edge){u, first[v], w}, first[v] = cnt++; } inline int NewNode(double w, int ed) { int x = ++tot; tr[x].w = w, tr[x].dis = 1, tr[x].ed = ed; return x; } int Merge(int x, int y) { if (!x || !y) return x + y; if (tr[x].w - tr[y].w >= eps) swap(x, y); int p = ++tot; tr[p] = tr[x], tr[p].rs = Merge(tr[p].rs, y); if (tr[tr[p].ls].dis < tr[tr[p].rs].dis) swap(tr[p].ls, tr[p].rs); tr[p].dis = tr[tr[x].rs].dis + 1; return p; } void Dfs(int u) { vis[u] = 1; for (int e = first[u], v; e != -1; e = edge[e].next) if (e & 1) { double w = edge[e].w; if (fabs(dis[u] + w - dis[v = edge[e].to]) < eps && !vis[v]) fa[v] = u, cov[e ^ 1] = 1, Dfs(v); } } int main() { memset(first, -1, sizeof(first)); memset(dis, 127, sizeof(dis)); scanf("%d%d%lf", &n, &m, &se); for (int i = 1, u, v; i <= m; ++i) scanf("%d%d%lf", &u, &v, &e), Add(u, v, e); dis[n] = 0, q.push(make_pair(0, n)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int e = first[u]; ~e; e = edge[e].next) if (e & 1) { int v = edge[e].to; if (dis[v] - (dis[u] + edge[e].w) >= eps) q.push(make_pair(-(dis[v] = dis[u] + edge[e].w), v)); } } for (int i = 1; i <= n; ++i) vis[i] = 0; Dfs(n); for (int e = 0, u, v; e < cnt; e += 2) if (!cov[e]) { u = edge[e ^ 1].to, v = edge[e].to; if (dis[u] == dis[0] || dis[v] == dis[0]) continue; rt[u] = Merge(rt[u], NewNode(dis[v] + edge[e].w - dis[u], v)); } for (int i = 1; i <= n; ++i) q.push(make_pair(-dis[i], i)); for (int i = 1, u; i <= n; ++i) { u = q.top().second, q.pop(); if (fa[u]) rt[u] = Merge(rt[u], rt[fa[u]]); } if (dis[1] - se < eps) se -= dis[1], ++ans; if (rt[1]) q.push(make_pair(-tr[rt[1]].w, rt[1])); while (!q.empty()) { int ed = q.top().second; double cur = q.top().first, w = dis[1] - cur; if (w - se >= eps) break; q.pop(), se -= w, ++ans; for (int i = 0; i < 2; ++i) { int nxt = i ? tr[ed].rs : tr[ed].ls; if (nxt) q.push(make_pair(cur + tr[ed].w - tr[nxt].w, nxt)); } if (rt[tr[ed].ed]) q.push(make_pair(cur - tr[rt[tr[ed].ed]].w, rt[tr[ed].ed])); } printf("%d\n", ans); return 0; } ```