【P2483 [模板] k 短路 / [SDOI2010]】魔法猪学院 题解(最短路树 + 可持久化左偏树)
Solution
1
基于最短路树的构建,我们可以得到终点到各个点的最短路。并以此为基础建树。
也即是说,对于树上的边,一定被包含在终点到某个点的最短路径上。
那么如何得到第
显然可以保证,用其他边替换当前的树边一定是不优的,即代价一定会增大。
2
这里其实是满足我自己的思考的:
-
对于路径的构造,这些路径的代价一定是相对有序/单调的。
-
我们弄一个小根堆维护答案,每次取队头得到前
小条路径。因为我们显然不能严格保证得到的答案是严格单调的。
实现第二个,其实就要满足每次我们取出的队头一定要更新新的状态(因为
在我的推论中,还存在一个棘手的问题:我们想要使用小根堆得到答案,就需要把所有可能的路径加进去。
那么显然我们不可能同时得到所有的路径,所以只能是从一个最优路径开始,放入优先队列,取出队头,以队头的状态为基础更新出若干个稍劣的路径方案并加入队列。这与上面的推论相符合。
3
最优方案是显然可以得到的,就是普通最短路。关键在于如何去不断更新出那个“稍劣的方案“。
这里就提到了开头的最短路树。
说白了,最短路树满足这样一个性质:
树根
到点 的简单路径就是 到 的最短路径。
这样一来,说明不在最短路树上的边,一定存在比它更优的路径能替代它,我们且叫它非树边。
为了更新状态,我们需要的显然就是这些非树边。
我们要解决的问题就是:如何适当地选择这些非树边去替代树边,能使新方案稍劣呢?
替代不免过于麻烦,我们不妨转化一下。
我们每次选出一个非树边边集,即为
进一步简化,我们每次计算的都是当前方案比最优方案多出多少花费。
这样以来我们无需考虑树边的选用与代价之和。我们只关心边集
同时,每条非树边的代价我们把它转化为
4
对于队头弹出来的方案边集
易证这样是合法的、能覆盖所有情况、且符合我们的要求的。
关键是我们要怎样用小根堆维护,在最短路树上,一个点到它的祖先们的所有非树边。
故而对于每个点,我们需要维护:
-
以这个点为起点的所有有向边,用一个最小堆维护。
-
这个点和它所有祖先的堆的并集。
对于这个东西,我们可以使用可持久化左偏树进行维护。
最终的时间复杂度题目背景已经提到,是
Code
#include<bits/stdc++.h>
using namespace std;
typedef double db;
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define pii pair<db, int>
#define mp make_pair
#define fr first
#define se second
const int maxn = 5e5 + 5, maxm = 2e5 + 5;
int n, m; db E;
db dis[maxn]; int Fa[maxn], ans; bool vis[maxn];
int sec[maxn], rt[maxn];
struct Edge{
int cnt, hd[maxn];
struct edge{
int to, nxt; db w;
}e[maxm];
inline edge& operator [](int x){ return e[x];}
inline void add(int u, int v, db w){ e[++cnt] = {v, hd[u], w}, hd[u] = cnt;}
}g, f;
struct tree{
int ls, rs; db vl; int td, fa;
};
struct Leftist_Tree{
tree t[maxn << 1]; int tot;
inline tree& operator [](int x){ return t[x];}
inline int nw(int f, db v){ t[++tot] = {0, 0, v, 0, f}; return tot;}
inline int merge(int a, int b){
if(!a or !b) return a + b;
if(t[a].vl > t[b].vl) swap(a, b);
int nw = ++tot; t[nw] = t[a];
t[nw].rs = merge(t[nw].rs, b);
if(t[t[nw].ls].td <= t[t[nw].rs].td) swap(t[nw].ls, t[nw].rs);
t[nw].td = t[nw].rs ? t[t[nw].rs].td + 1 : 0;
return nw;
}
}T;
inline void dijkstra(){
priority_queue<pii, vector<pii>, greater<pii> > pq;
rep(i, 1, n) dis[i] = 1e8 * 1.0;
pq.push(mp(0.0, n)), dis[n] = 0;
while(pq.size()){
pii nw = pq.top(); pq.pop();
if(vis[nw.se]) continue; vis[nw.se] = 1;
for(int i = f.hd[nw.se]; i; i = f[i].nxt){
int v = f[i].to;
if(dis[v] > nw.fr + f[i].w)
dis[v] = nw.fr + f[i].w, Fa[v] = i,
pq.push(mp(dis[v], v));
}
}
}
inline bool cmp(int a, int b){ return dis[a] < dis[b];}
priority_queue<pii, vector<pii>, greater<pii> > pq;
int main(){
scanf("%d%d%lf", &n, &m, &E); rep(i, 1, n) sec[i] = i;
rep(i, 1, m){
int u, v; db w; scanf("%d%d%lf", &u, &v, &w);
if(u == n){ i -= 1, m -= 1; continue;}
g.add(u, v, w), f.add(v, u, w);
}
dijkstra();
sort(sec + 1, sec + n + 1, cmp);
T[0].td = -1;
rep(j, 1, n){
int x = sec[j];
for(int i = g.hd[x]; i; i = g[i].nxt) if(i != Fa[x]){
int v = g[i].to;
rt[x] = T.merge(rt[x], T.nw(g[i].to, -dis[x] + g[i].w + dis[g[i].to]));
} rt[x] = T.merge(rt[x], rt[g[Fa[x]].to]);
}
E -= dis[1], ans += 1;
pq.push(mp(T[rt[1]].vl, rt[1]));
while(pq.size()){
pii nw = pq.top(); pq.pop();
if(E < nw.fr + dis[1]) return printf("%d", ans), 0;
E -= nw.fr + dis[1], ans += 1;
if(T[nw.se].ls) pq.push(mp(nw.fr - T[nw.se].vl + T[T[nw.se].ls].vl, T[nw.se].ls));
if(T[nw.se].rs) pq.push(mp(nw.fr - T[nw.se].vl + T[T[nw.se].rs].vl, T[nw.se].rs));
if(rt[T[nw.se].fa]) pq.push(mp(nw.fr + T[rt[T[nw.se].fa]].vl, rt[T[nw.se].fa]));
}
return printf("%d", ans), 0;
}
Thanks for reading.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人