Luogu2483 [SDOI2010]魔法豬學院(可並堆)
對於原圖以 \(t\) 為根建出任意一棵最短路徑樹 \(T\),即反著從 \(t\) 跑出到所有點的最短路 \(dis\)
它有一些性質:
性質1:
對於一條 \(s\) 到 \(t\) 的路徑的邊集 \(P\),去掉 \(P\) 中和 \(T\) 的交集,記為 \(P'\)。
那麼 \(P'\) 對於中任意相鄰(從 \(s\) 到 \(t\) 的順序)的兩條邊 \(e,f\),滿足 \(f\) 的起點在 \(T\) 中為 \(e\) 的終點的祖先或者為相同點。
因為 \(P\) 中 \(e,f\) 之間由樹邊相連或者直接相連。
性質2:
對於不在 \(T\)
定義 \(\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\) 為空集(實際上只要維護邊集當前尾部的邊的起點是哪一個就好了,空集即 \(s\))
每次取出最小權值的邊集 \(P\),設當前尾部的邊的起點為 \(x\)
有兩種方法可以得到一個新的邊集:
1.替換 \(x\) 為起點的這條邊為一條剛好大於等於它的非樹邊。
2.尾部接上一條起點為以 \(x\) 為起點的這條邊的終點在 \(T\) 中祖先(包括自己)連出去的所有非樹邊的最小邊。
然後就是怎麼維護祖先出去的所有非樹邊的最小邊:
顯然可以從祖先轉移過來,直接可並堆即可。
又因為要保留每個點的資訊,所以合併的時候可持久化即可
和線段樹合併的可持久化一樣,然後就可以過了。
建議可以看一看課件
# 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;
}