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\) 中的边 \(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\) 中祖先(包括自己)连出去的所有非树边的最小边。

然后就是怎么维护祖先出去的所有非树边的最小边:

显然可以从祖先转移过来,直接可并堆即可。

又因为要保留每个点的信息,所以合并的时候可持久化即可

和线段树合并的可持久化一样,然后就可以过了。

建议可以看一看课件

# 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;
}
posted @ 2018-11-02 14:15  Cyhlnj  阅读(208)  评论(0编辑  收藏  举报