【学习笔记】左偏树的可持久化(【模板】k短路 / [SDOI2010]魔法猪学院)


【模板】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 uv u u u为这条边的起点, v v v为这条边的终点

显然,如果一条原边出现在 T T T上,那么 v v v一定是 u u u的祖先

这个最短路径树有几个性质

  • 性质Ⅰ

    对于一条 s → t s\rightarrow t st的路径,将这条路径依次经过的边的集合,记为边集 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 st来看) 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+wdisu,即选这条边与最短路的差值

    L E L_E LE为一条路径的长度,这条路径为 s → t s\rightarrow t st

    显然有 L E = ∑ e ∈ E ′ Δ e + d i s s L_E=\sum_{e\in E'}\Delta_e+dis_s LE=eEΔe+diss

  • 性质Ⅲ

    对于满足性质Ⅰ的中 E ′ E' E的定义的边集 P P P,有且只有一条 s → t s\rightarrow t st的路径 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值