【P2483 [模板] k 短路 / [SDOI2010]】魔法猪学院 题解(最短路树 + 可持久化左偏树)

Link.

最短路树 + 可持久化左偏树

Solution

1

基于最短路树的构建,我们可以得到终点到各个点的最短路。并以此为基础建树。

也即是说,对于树上的边,一定被包含在终点到某个点的最短路径上。

那么如何得到第 k 大的路径呢?无非就是替换掉这些树边,以此计算额外的代价,这也是建最短路树的目的。

显然可以保证,用其他边替换当前的树边一定是不优的,即代价一定会增大。

2

这里其实是满足我自己的思考的:

  • 对于路径的构造,这些路径的代价一定是相对有序/单调的。

  • 我们弄一个小根堆维护答案,每次取队头得到前 k 小条路径。因为我们显然不能严格保证得到的答案是严格单调的。

实现第二个,其实就要满足每次我们取出的队头一定要更新新的状态(因为 k 的上限在过程中是不定的),而每次更新的新状态一定是比这个装填要更劣的。

在我的推论中,还存在一个棘手的问题:我们想要使用小根堆得到答案,就需要把所有可能的路径加进去。

那么显然我们不可能同时得到所有的路径,所以只能是从一个最优路径开始,放入优先队列,取出队头,以队头的状态为基础更新出若干个稍劣的路径方案并加入队列。这与上面的推论相符合。

3

最优方案是显然可以得到的,就是普通最短路。关键在于如何去不断更新出那个“稍劣的方案“。

这里就提到了开头的最短路树。

说白了,最短路树满足这样一个性质:

树根 T 到点 i 的简单路径就是 Ti 的最短路径。

这样一来,说明不在最短路树上的边,一定存在比它更优的路径能替代它,我们且叫它非树边。

为了更新状态,我们需要的显然就是这些非树边。

我们要解决的问题就是:如何适当地选择这些非树边去替代树边,能使新方案稍劣呢?

替代不免过于麻烦,我们不妨转化一下。

我们每次选出一个非树边边集,即为 S。每次的方案使用的非树边即为 S。而将这些非树边连接起来的,就是树边。

进一步简化,我们每次计算的都是当前方案比最优方案多出多少花费。

这样以来我们无需考虑树边的选用与代价之和。我们只关心边集 S 中非树边的代价之和。

同时,每条非树边的代价我们把它转化为 ei.w=ei.w(disudisv)=ei.wdisu+disv。意为使用了这条非树边后,比最优方案多出多少代价。

4

对于队头弹出来的方案边集 S,我们不妨取出它最晚加入的一条非树边,替换成第一个比它花费多、同起点的非树边,或者保留并新加入一条非树边。

易证这样是合法的、能覆盖所有情况、且符合我们的要求的。

关键是我们要怎样用小根堆维护,在最短路树上,一个点到它的祖先们的所有非树边。

故而对于每个点,我们需要维护:

  1. 以这个点为起点的所有有向边,用一个最小堆维护。

  2. 这个点和它所有祖先的堆的并集。

对于这个东西,我们可以使用可持久化左偏树进行维护。

最终的时间复杂度题目背景已经提到,是 O((n+m)logn+klogk) 的。相比 A* 的 O(nklogn),其优越性显然。

Code

2.6 KB,压行见谅。

#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.

posted @   pldzy  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示