【模板】k短路 / [SDOI2010]魔法猪学院
description
iPig 在假期来到了传说中的魔法猪学院,开始为期两个月的魔法猪训练。经过了一周理论知识和一周基本魔法的学习之后,iPig 对猪世界的世界本原有了很多的了解:众所周知,世界是由元素构成的;元素与元素之间可以互相转换;能量守恒 … … … …\ldots… ………
iPig 今天就在进行一个麻烦的测验。iPig 在之前的学习中已经知道了很多种元素,并学会了可以转化这些元素的魔法,每种魔法需要消耗 iPig 一定的能量。作为 PKU 的顶尖学猪,让 iPig 用最少的能量完成从一种元素转换到另一种元素…等等,iPig 的魔法导猪可没这么笨!这一次,他给 iPig 带来了很多 1 号元素的样本,要求 iPig 使用学习过的魔法将它们一个个转化为 N 号元素,为了增加难度,要求每份样本的转换过程都不相同。这个看似困难的任务实际上对 iPig 并没有挑战性,因为,他有坚实的后盾…现在的你呀!
注意,两个元素之间的转化可能有多种魔法,转化是单向的。转化的过程中,可以转化到一个元素(包括开始元素)多次,但是一但转化到目标元素,则一份样本的转化过程结束。iPig 的总能量是有限的,所以最多能够转换的样本数一定是一个有限数。具体请参看样例。
输入格式
第一行三个数 N,M,E,表示 iPig 知道的元素个数(元素从 1 到 N 编号),iPig 已经学会的魔法个数和 iPig 的总能量。
后跟 M 行每行三个数 si,ti,ei 表示 iPig 知道一种魔法,消耗 ei 的能量将元素 si 变换到元素 ti
solution
对于原图以终点 t t t为根建立一棵任意的最短路径树 T T T,即我们所谓的反图
从 t t t跑出到所有点的最短路 d i s dis dis
显然,从点 s s s到终点 t t t可能有不止一条路径,也有可能不止一条最短路
如果两条边都能出现在 T T T上,且两条边又只能出现一条,那么随便选一条即可
定义:对于一条原图边 u → v u\rightarrow v u→v, u u u为这条边的起点, v v v为这条边的终点
显然,如果一条原边出现在 T T T上,那么 v v v一定是 u u u的祖先
这个最短路径树有几个性质
-
性质Ⅰ
对于一条 s → t s\rightarrow t s→t的路径,将这条路径依次经过的边的集合,记为边集 E E E,或路径 E E E
然后去掉 E E E与 T T T的交集,记为边集 E ′ E' E′,或路径 E ′ E' E′
也就是说,一条路径 P P P指代的是这条路径包含的所有边的集合
那么对于 E ′ E' E′中任意相邻的两条边(从原图的方向 s → t s\rightarrow t s→t来看) a , b a,b a,b,即先走边 a a a再走边 b b b
满足 b b b的起点在 T T T中为 a a a的终点的祖先或者相同点
因为 a , b a,b a,b两边之间要么由树边相连,要么直接相连
-
性质Ⅱ
对于不在 T T T的一条边 e e e, u , v u,v u,v分别为起点/终点,边权为 w w w
定义 Δ e = d i s v + w − d i s u \Delta_e=dis_v+w-dis_u Δe=disv+w−disu,即选这条边与最短路的差值
设 L E L_E LE为一条路径的长度,这条路径为 s → t s\rightarrow t s→t
显然有 L E = ∑ e ∈ E ′ Δ e + d i s s L_E=\sum_{e\in E'}\Delta_e+dis_s LE=∑e∈E′Δe+diss
-
性质Ⅲ
对于满足性质Ⅰ的中 E ′ E' E′的定义的边集 P P P,有且只有一条 s → t s\rightarrow t s→t的路径 E E E,使得 P = E ′ P=E' P=E′
因为 T T T上任意两点之间有且仅有一条路径
这道题就转化为了求第 K K K小满足性质Ⅰ的路径 E ′ E' E′的定义的边集
最短路径肯定是 T T T上的 d i s 1 dis_1 dis1
用小根堆维护边集 E E E,初始为空集(事实上只要维护当前刻画出的路径的最后一条边的起点(尾端)即可,空集就是整条路径的起点 s s s,本题中就是点 1 1 1)
一个点可以延伸多条边,也就刻画出了多条不同的路径,这些路径都需要分开记录
对于已刻画的所有路径,取出最小权值的路径 E E E,设当前尾部边的起点为 u u u
有两种新路径的刻画
- 将这个边集的最后一条边,替换成另外一条以 u u u为起点的权值大于等于当前边权值的非树边
- 这条最后边的终点为 v v v,在这条边的后面接一条 在 T T T中为 v v v祖先(含 v v v自身)的点 能延伸的所有非树边的 最小边
问题就是怎么维护祖先延伸的所有非树边的最小边
从祖先转移过来,最小堆合并即可
每个点的自身信息也要保留,不能和祖先的边混合,这是为了转移Ⅰ,替换最后一条边
那么可持久化即可
⚠:最后注意一下,以
n
n
n为起点的边(这种魔法可以将
n
n
n元素转化成其他元素)是不能要的
因为本题,到了
n
n
n元素就意味着这次转化成功了,就此就终止了
你谷第一个数据点就是卡的这个小细节
code
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define Pair pair < double, int >
#define inf 0x7f7f7f7f
#define maxm 200005
#define maxn 5005
struct edge { int u, v; double w; }E[maxm];
struct node { int lson, rson, dis, End; double val; }t[maxm * 50];
priority_queue < Pair, vector < Pair >, greater < Pair > > q;
vector < int > G[maxn];
int n, m, cnt;
double En;
int f[maxn], root[maxn];
double dis[maxn];
bool vis[maxn], used[maxm];
int Newnode( double val, int End ) {
int now = ++ cnt;
t[now].End = End;
t[now].val = val;
return now;
}
int merge( int x, int y ) {
if( ! x or ! y ) return x + y;
if( t[x].val > t[y].val ) swap( x, y );
int now = ++ cnt;
t[now] = t[x];
t[now].rson = merge( t[x].rson, y );
if( t[t[now].lson].dis < t[t[now].rson].dis )
swap( t[now].lson, t[now].rson );
t[now].dis = t[t[now].rson].dis + 1;
return now;
}
void dfs( int u ) {
vis[u] = 1;
for( auto id : G[u] ) {
auto v = E[id].v;
auto w = E[id].w;
if( ! vis[v] and dis[v] == dis[u] + w )
f[v] = u, used[id] = 1, dfs( v );
}
}
int main() {
scanf( "%d %d %lf", &n, &m, &En );
for( int i = 1;i <= m;i ++ ) {
scanf( "%d %d %lf", &E[i].v, &E[i].u, &E[i].w );
if( E[i].v == n ) i --, m --;
else G[E[i].u].push_back( i );
}
memset( dis, 0x7f, sizeof( dis ) );
q.push( make_pair( dis[n] = 0, n ) );
while( ! q.empty() ) {
int u = q.top().second; q.pop();
if( vis[u] ) continue;
vis[u] = 1;
for( auto id : G[u] ) {
auto v = E[id].v;
auto w = E[id].w;
if( dis[v] > dis[u] + w )
q.push( make_pair( dis[v] = dis[u] + w, v ) );
}
}
memset( vis, 0, sizeof( vis ) );
dfs( n );
for( int i = 1;i <= m;i ++ )
if( used[i] ) continue;
else {
auto u = E[i].u;
auto v = E[i].v;
auto w = E[i].w;
if( dis[u] == inf or dis[v] == inf ) continue;
else root[v] = merge( root[v], Newnode( dis[u] + w - dis[v], u ) );
}
for( int i = 1;i <= n;i ++ ) q.push( make_pair( dis[i], i ) );
while( ! q.empty() ) {
int now = q.top().second; q.pop();
if( f[now] ) root[now] = merge( root[now], root[f[now]] );
}
int ans = 0;
if( dis[1] <= En ) En -= dis[1], ans ++;
if( root[1] ) q.push( make_pair( t[root[1]].val, root[1] ) );
while( ! q.empty() ) {
auto w = q.top().first;
auto now = q.top().second;
q.pop();
if( dis[1] + w > En ) break;
else ans ++, En -= dis[1] + w;
if( t[now].lson ) //转移一 替换最后一条边
q.push( make_pair( w - t[now].val + t[t[now].lson].val, t[now].lson ) );
if( t[now].rson )
q.push( make_pair( w - t[now].val + t[t[now].rson].val, t[now].rson ) );
if( root[t[now].End] ) //转移二 找边终点所有祖先的可扩展最小非树边
q.push( make_pair( w + t[root[t[now].End]].val, root[t[now].End] ) );
}
printf( "%d\n", ans );
return 0;
}