文章目录
- description
- solution
- code
【模板】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
对于原图以终点ttt为根建立一棵任意的最短路径树TTT,即我们所谓的反图
从ttt跑出到所有点的最短路disdisdis
显然,从点sss到终点ttt可能有不止一条路径,也有可能不止一条最短路
如果两条边都能出现在TTT上,且两条边又只能出现一条,那么随便选一条即可
定义:对于一条原图边u→vu\rightarrow vu→v,uuu为这条边的起点,vvv为这条边的终点
显然,如果一条原边出现在TTT上,那么vvv一定是uuu的祖先
这个最短路径树有几个性质
-
性质Ⅰ
对于一条s→ts\rightarrow ts→t的路径,将这条路径依次经过的边的集合,记为边集EEE,或路径EEE
然后去掉EEE与TTT的交集,记为边集E′E'E′,或路径E′E'E′
也就是说,一条路径PPP指代的是这条路径包含的所有边的集合
那么对于E′E'E′中任意相邻的两条边(从原图的方向s→ts\rightarrow ts→t来看)a,ba,ba,b,即先走边aaa再走边bbb
满足bbb的起点在TTT中为aaa的终点的祖先或者相同点
因为a,ba,ba,b两边之间要么由树边相连,要么直接相连
-
性质Ⅱ
对于不在TTT的一条边eee,u,vu,vu,v分别为起点/终点,边权为www
定义Δe=disv+w−disu\Delta_e=dis_v+w-dis_uΔe=disv+w−disu,即选这条边与最短路的差值
设LEL_ELE为一条路径的长度,这条路径为s→ts\rightarrow ts→t
显然有LE=∑e∈E′Δe+dissL_E=\sum_{e\in E'}\Delta_e+dis_sLE=∑e∈E′Δe+diss
-
性质Ⅲ
对于满足性质Ⅰ的中E′E'E′的定义的边集PPP,有且只有一条s→ts\rightarrow ts→t的路径EEE,使得P=E′P=E'P=E′
因为TTT上任意两点之间有且仅有一条路径
这道题就转化为了求第KKK小满足性质Ⅰ的路径E′E'E′的定义的边集
最短路径肯定是TTT上的dis1dis_1dis1
用小根堆维护边集EEE,初始为空集(事实上只要维护当前刻画出的路径的最后一条边的起点(尾端)即可,空集就是整条路径的起点sss,本题中就是点111)
一个点可以延伸多条边,也就刻画出了多条不同的路径,这些路径都需要分开记录
对于已刻画的所有路径,取出最小权值的路径EEE,设当前尾部边的起点为uuu
有两种新路径的刻画
- 将这个边集的最后一条边,替换成另外一条以uuu为起点的权值大于等于当前边权值的非树边
- 这条最后边的终点为vvv,在这条边的后面接一条 在TTT中为vvv祖先(含vvv自身)的点 能延伸的所有非树边的 最小边
问题就是怎么维护祖先延伸的所有非树边的最小边
从祖先转移过来,最小堆合并即可
每个点的自身信息也要保留,不能和祖先的边混合,这是为了转移Ⅰ,替换最后一条边
那么可持久化即可
⚠:最后注意一下,以nnn为起点的边(这种魔法可以将nnn元素转化成其他元素)是不能要的
因为本题,到了nnn元素就意味着这次转化成功了,就此就终止了
你谷第一个数据点就是卡的这个小细节
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;
}