【学习笔记】左偏树的可持久化(【模板】k短路 / [SDOI2010]魔法猪学院)
文章目錄
- 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; }總結
以上是生活随笔為你收集整理的【学习笔记】左偏树的可持久化(【模板】k短路 / [SDOI2010]魔法猪学院)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓红外线在哪里打开(安卓红外线)
- 下一篇: 小梅哥——38译码器