题目:
题目背景
注:对于 kk 短路问题,A* 算法的最坏时间复杂度是 O(nk \log n)O(nklogn) 的。虽然 A* 算法可以通过本题原版数据,但可以构造数据,使得 A* 算法在原题的数据范围内无法通过。事实上,存在使用可持久化可并堆的算法可以做到在 O((n+m) \log n + k \log k)O((n+m)logn+klogk) 的时间复杂度解决 kk 短路问题。详情见 OI-Wiki。
题目描述
iPig 在假期来到了传说中的魔法猪学院,开始为期两个月的魔法猪训练。经过了一周理论知识和一周基本魔法的学习之后,iPig 对猪世界的世界本原有了很多的了解:众所周知,世界是由元素构成的;元素与元素之间可以互相转换;能量守恒\ldots…。
iPig 今天就在进行一个麻烦的测验。iPig 在之前的学习中已经知道了很多种元素,并学会了可以转化这些元素的魔法,每种魔法需要消耗 iPig 一定的能量。作为 PKU 的顶尖学猪,让 iPig 用最少的能量完成从一种元素转换到另一种元素\ldots…等等,iPig 的魔法导猪可没这么笨!这一次,他给 iPig 带来了很多 11 号元素的样本,要求 iPig 使用学习过的魔法将它们一个个转化为 NN 号元素,为了增加难度,要求每份样本的转换过程都不相同。这个看似困难的任务实际上对 iPig 并没有挑战性,因为,他有坚实的后盾\ldots…现在的你呀!
注意,两个元素之间的转化可能有多种魔法,转化是单向的。转化的过程中,可以转化到一个元素(包括开始元素)多次,但是一但转化到目标元素,则一份样本的转化过程结束。iPig 的总能量是有限的,所以最多能够转换的样本数一定是一个有限数。具体请参看样例。
输入格式
第一行三个数 N, M, EN,M,E,表示 iPig 知道的元素个数(元素从 11 到 NN 编号),iPig 已经学会的魔法个数和 iPig 的总能量。
后跟 MM 行每行三个数 s_i, t_i, e_is
i
,t
i
,e
i
表示 iPig 知道一种魔法,消耗 e_ie
i
的能量将元素 s_is
i
变换到元素 t_it
i
。
输出格式
一行一个数,表示最多可以完成的方式数。输入数据保证至少可以完成一种方式。
输入输出样例
输入 #1复制
4 6 14.9
1 2 1.5
2 1 1.5
1 3 3
2 3 1.5
3 4 1.5
1 4 1.5
输出 #1复制
3
说明/提示
有意义的转换方式共 44 种:
1\to 41→4,消耗能量 1.51.5。
1\to 2\to 1\to 41→2→1→4,消耗能量 4.54.5。
1\to3\to41→3→4,消耗能量 4.54.5。
1\to2\to3\to41→2→3→4,消耗能量 4.54.5。
显然最多只能完成其中的 33 种转换方式(选第一种方式,后三种方式任选两个),即最多可以转换 33 份样本。
如果将 E=14.9E=14.9 改为 E=15E=15,则可以完成以上全部方式,答案变为 44。
数据规模
占总分不小于 10%10% 的数据满足 N \leq 6,M \leq 15N≤6,M≤15。
占总分不小于 20%20% 的数据满足 N \leq 100,M \leq 300,E\leq100N≤100,M≤300,E≤100 且 EE 和所有的 e_ie
i
均为整数(可以直接作为整型数字读入)。
所有数据满足 2 \leq N \leq 50002≤N≤5000,1 \leq M \leq 2000001≤M≤200000,1 \leq E \leq 10 ^ 71≤E≤10
7
,1 \leq ei\leq E1≤ei≤E,EE 和所有的 e_ie
i
为实数。
解析
对于原图以 tt 为根建出任意一棵最短路径树 TT,即反着从 tt 跑出到所有点的最短路 disdis
它有一些性质:
性质1:
对于一条 ss 到 tt 的路径的边集 PP,去掉 PP 中和 TT 的交集,记为 P’P
′
。
那么 P’P
′
对于中任意相邻(从 ss 到 tt 的顺序)的两条边 e,fe,f,满足 ff 的起点在 TT 中为 ee 的终点的祖先或者为相同点。
因为 PP 中 e,fe,f 之间由树边相连或者直接相连。
性质2:
对于不在 TT 中的边 ee ,设 uu 为起点,vv 为终点,ww为权值。
定义 \Delta_e=dis_v+w-dis_uΔ
e
=dis
v
+w−dis
u
,即选这条边的路径和最短路的长度的差
设 L_PL
P
表示路径长度,则有
L_P=dis_s+\sum_{e\in p’}\Delta_e
L
P
=dis
s
+
e∈p
′
∑
Δ
e
这很显然。
性质3:
对于满足性质 11 的 P’P
′
的定义的边集 SS,有且仅有一条 ss 到 tt 的路径的边集 PP,使得 P’=SP
′
=S。
因为树 TT 上的两个点之间有且仅有一条路径。
问题转化
求第 kk 小的满足性质 11 的 P’P
′
的定义的边集
算法
用小根堆维护边集 PP
初始 PP 为空集(实际上只要维护边集当前尾部的边的起点是哪一个就好了,空集即 ss)
每次取出最小权值的边集 PP,设当前尾部的边的起点为 xx
有两种方法可以得到一个新的边集:
**1.**替换 xx 为起点的这条边为一条刚好大于等于它的非树边。
**2.**尾部接上一条起点为以 xx 为起点的这条边的终点在 TT 中祖先(包括自己)连出去的所有非树边的最小边。
然后就是怎么维护祖先出去的所有非树边的最小边:
显然可以从祖先转移过来,直接可并堆即可。
又因为要保留每个点的信息,所以合并的时候可持久化即可
和线段树合并的可持久化一样,然后就可以过了。
建议可以看一看课件
# 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;
}