专栏文章

Rank-Pairing Heap(赋级配对堆)学习笔记

算法·理论参与者 31已保存评论 37

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
37 条
当前快照
1 份
快照标识符
@mhz5saxd
此快照首次捕获于
2025/11/15 01:55
4 个月前
此快照最后确认于
2025/11/29 05:24
3 个月前
查看原文
Rank-Pairing Heap(rp-heap,赋级配对堆)是一种堆。(废话
复杂度和Fib堆相同(如果不知道Fib堆什么复杂度,百度,请)。
论文里说rp堆更优秀(理由是维护的信息更少),但wiki说Fib堆更快一些。
不过至少我觉得rp-heap要好写得多(论文里好像也这么说了)。毕竟我这种菜鸡都能写出来
这里是论文课件,都是嘤文的。(Tarjan大毒瘤)
还有EtaoinWu的《配对堆与赋级配对堆(Rank Pairing Heaps)》。本文的大部分中文译名参考自此。
先膜为敬,EtaoinWu Orz
本文中采用中文还是英文名似乎很混乱。
写blog不易,给个赞吧(
以下默认是最小堆(小根堆)。
前置知识 : 二叉树,渐进记号,摊还分析,初中一年级数学(废话
不必要的前置知识 : 配对堆,二项堆,Fib堆
说几句闲话,学习Fib堆可以参考洛谷日报 :
但是个人认为这篇文章讲得不够详细(就连势函数都是不完整的),如果想详细了解,还是推荐算法导论,或者这篇blog
想要代码的话,LOJ的最短路板子按代码长度排序,第一页有两个Fib堆实现,分别是lcomyn373378qiyue7187001。在上面那篇日报评论区和那个blog里也有。看出来Fib堆难写了

0.一些记号

HH表示一个rp-heap,x,yx,y表示结点或数值,keykey表示关键字。
left\operatorname{left}right\operatorname{right}分别表示左儿子和右儿子。
insert(x,H)=merge(make-heap(x),H)\operatorname{insert}(x,H)=\operatorname{merge}(\operatorname{make-heap}(x),H)
delete(p,H)=decrease-key(p,inf,H),extract-min(H)\operatorname{delete}(p,H)=\operatorname{decrease-key}(p,-\inf,H),\operatorname{extract-min}(H)
文中提到"其它操作复杂度"时,不包括以上两操作的复杂度。

1.半树(half tree)

1.1.定义

rp-heap是由若干半树(half tree\text{half tree})组成的。
half-ordered\text{half-ordered}的定义是 : 每个结点的keykey比左子树任意结点的keykey小。
半树的定义是 : 根节点没有右儿子,且所有点满足half-ordered\text{half-ordered}性质的二叉树。(请仔细读这句话
由此可以立刻得出根是树中最小的。
link\operatorname{link}是半树的基本操作,用于把两棵半树合并成一棵半树。它的过程如下 :
link\operatorname{link}接受两棵半树的根x,yx,y作为输入。不妨假设xxkeykey更小。把xx的左儿子拿下来,接到yy的右儿子上,然后把yy接到xx的左儿子上。这是O(1)O(1)的。
这是从论文里拿的图 :
link
显然合并后仍然满足半树定义。

1.3.rank\operatorname{rank}(级)

定义rank:VN\operatorname{rank}:V \to \mathbb{N}。单个节点的rank\operatorname{rank}00。定义一棵半树的rank\operatorname{rank}是根的rank\operatorname{rank}值。
我们规定,rank\operatorname{rank}值相同的半树能进行fair-link\operatorname{fair-link}。操作之后,具有较大keykey的根rank+1\operatorname{rank}+1,其它结点不变。还是从论文拿的图 :
fair-link
发现如果从若干只有一个结点的半树开始,不管怎样fair-link\operatorname{fair-link},产生的半树的形态都是根的左子树挂着一个满二叉树。
更进一步,发现这样的半树跟二项树本质是相同的。画个图解释一下 :
half tree and binomial tree
(希望dalao们可以给我一个能做这样动图的软件qwq)
所以这个数据结构可以看作二项堆的优化。(Fib堆似乎也是qwq)
实际上论文似乎就是这么认为的。
但由于复杂度分析里面用到了半树上的兄弟,使用二项树的理论可能会很难受。

2.rp-heap的结构

rp-heap使用一个称为根链表的双向循环链表串起一串的半树根(但是实际上根链表存在仅仅是为了方便插入删除,并不表示任何顺序),并且维护堆中有着最小值的结点的指针H.minH.min。显然,H.minH.min一定是某个根。

3.一个不够优秀的复杂度

对于merge\operatorname{merge}insert\operatorname{insert},我们直接连接/插入根链表。
对于extract-min\operatorname{extract-min},和二项堆一样,把H.minH.min删掉后,把它左子树的右链(或右脊柱,即从根一直走右儿子直到没有右儿子得到的路径)上所有边断开,得到若干棵半树(因为每个点都满足half-ordered\text{half-ordered},并且断开右链后右链上的点失去了右儿子)。将这个操作称为disassembly\operatorname{disassembly}(拆分)。
接下来把这些半树和根链表里的其它根一起进行一轮配对(One-pass link\text{One-pass link}) : 对每个rank\operatorname{rank}建立一个桶,先遍历disassembly\operatorname{disassembly}产生的半树根再遍历根链表,把当前半树扔进对应的桶里。如果桶里已经有树,把它们fair-link\operatorname{fair-link}起来,并删掉根链表里面两棵树的根,直接在根链表加入link\operatorname{link}后的新根。最后把桶里剩下的也扔进根链表。
对于decrease-key\operatorname{decrease-key},为了达到O(1)O(1),我们用被减值的结点xx的右子树替换它原来的位置,把它和它的左子树拿出来扔到根链表里,这个操作称为对xx的一次cut\operatorname{cut}(切断)。这样我们才能不必让xx上浮。
但是它破坏了rank\operatorname{rank}rr的二项树至少有2r2^r个结点的性质(你把一部分拆出来了,结点数就不够了),因此可以造出rank\operatorname{rank}达到ω(logn)\omega(\log n)的半树,这样extract-min\operatorname{extract-min}就需要使用ω(logn)\omega(\log n)个桶。
这个东西可以摊到除了extract-min\operatorname{extract-min}O(logn)O(\log n)的(没有decrease-key\operatorname{decrease-key}的情况下),其它都是O(1)O(1)的,即不带decrease-key\operatorname{decrease-key}的Pairing Heap复杂度。因为我看不懂英文这不是重点,所以这里不给出证明。有兴趣可以自行阅读论文。
为了得到高效的decrease-key\operatorname{decrease-key},我们需要放宽一些限制,使rank\operatorname{rank}O(logn)O(\log n)的性质不会被decrease-key\operatorname{decrease-key}打破后难以复原,并在优秀的实现中保持该性质。

4.一个优秀的复杂度

定义Δrank(x)=rank(fa(x))rank(x)\Delta \operatorname{rank}(x)=\operatorname{rank}(\operatorname{fa}(x))-\operatorname{rank}(x),称左儿子Δrank\Delta \operatorname{rank}ii的点为一个ii-结点,两个儿子Δrank\Delta \operatorname{rank}分别为i,ji,j(不分顺序)的点为一个i,ji,j-结点。
在之前的做法和标准的二项堆里,每个根是11-结点,每个内部结点是1,11,1结点。rp-heap的核心思想是放宽内部结点的限制。
我们引入两种rank rule\text{rank rule},它们是rp-heap对半树rank\operatorname{rank}的限制。
Type-1(甲类)条件 : 每个根是11-结点,而每个内部结点要么是1,11,1-结点,要么是0,?0,?-结点,其中??是任意正整数。可以发现一棵满足Type-1条件的rank\operatorname{rank}rr的半树至少有2r2^r个结点。
Type-2(乙类)条件 : 每个根是11-结点,而每个内部结点是1,11,1-结点,1,21,2-结点或0,?0,?-结点,其中??是任意正整数。可以发现一棵满足Type-2条件的rank\operatorname{rank}rr的半树至少有Fibonaccir+2ϕr\text{Fibonacci}_ {r+2}\geq\phi^r个结点。
因此不管使用哪一种rank rule\text{rank rule},最大rank\operatorname{rank}O(logn)O(\log n)的。
这不会影响前面其它操作的正确性。
接下来我们可以尝试实现decrease-key\operatorname{decrease-key}了。
如果被减值结点xx是根,不用做任何结构操作。
否则,先对xx进行一次cut\operatorname{cut},并把rank(x)\operatorname{rank}(x)设为rank(left(x))+1\operatorname{rank}(\operatorname{left}(x))+1。然后从xx原来的父亲开始,判断当前结点是否仍然满足rank rule\text{rank rule},如果不满足,则把当前结点的rank\operatorname{rank}减少到满足,并继续处理当前结点的父亲。如果没有破坏条件就结束。
这是论文中的图 : (图中是Type-1 rp-heap)
decrease-key on a Type-1 rp-heap
现在你知道这个数据结构名字的由来了,它使用类似于配对堆的配对方式,并用特殊的rank rule\text{rank rule}来放松二项堆对结点rank\operatorname{rank}的限制,从而得到优秀的复杂度。
Fib堆也用到了类似的思想 : 在Fib堆里,如果一个结点要破坏使extract-min\operatorname{extract-min}复杂度正确的性质时,就把它也拆下来,并把拆下来这部分的复杂度摊掉。一会你将看到,rp堆也会摊掉保持rank rule\text{rank rule}的代价。
说句闲话,看看进度条

5.复杂度分析

下面的内容比较毒瘤并且有很多显然,请自行选择是否看完。不想看可以向下翻到代码部分。
这里只分析常数更小的Type-2 rp-heap。对Type-1有兴趣的话,反正我不会请自行阅读论文。
定义一个结点uu的基本势(base potential\text{base potential})为,设uu是一个i,ji,j-结点(uu只有一个儿子则视j=0j=0),则基本势为i+j1i+j-1,即Δrank(left(u))+Δrank(right(u))1\Delta \operatorname{rank}(\operatorname{left}(u))+\Delta \operatorname{rank}(\operatorname{right}(u))-1
(这个译名是我口胡的,如果不标准,希望神仙们可以指出来)
定义一个结点uu的额外势(extra potential\text{extra potential})为 :
  • 如果uu是根,额外势为11
  • 如果uu1,11,1-结点,额外势是1-1
  • 否则,额外势是00
(这个译名也是我口胡的,如果不标准,也希望神仙们可以指出来)
定义一个结点uu的势为基本势和额外势的和。则立刻可得 :
  • 如果uu是根,势为11
  • 如果uu1,11,1-结点,势为00
  • 否则,设uui,ji,j-结点,势为i+j1i+j-1
定义势函数为所有结点势的和。
merge\operatorname{merge}find-min\operatorname{find-min}实际代价是O(1)O(1)的并且不改变势。make-heap\operatorname{make-heap}insert\operatorname{insert}实际代价是O(1)O(1)的并且将势增加11。所以这四个操作摊还复杂度都是O(1)O(1)
考虑extract-min\operatorname{extract-min}。定义hhdisassembly\operatorname{disassembly}后半树的总数。显然,extract-min\operatorname{extract-min}的实际代价是O(h+logn)O(h+\log n)(logn\log n是操作桶的时间,如果你每次最后遍历桶而不是记录非空桶的话)。考虑被删除根的左子树右链上最多有logϕn\log_{\phi} n1,11,1-结点(每个使rank\operatorname{rank}减少11),它们变成根之后会增加最多logϕn\log_{\phi} n的势。除此之外其它任何结点变成根都不会使势增加,所以disassembly\operatorname{disassembly}使势增加最多logϕn\log_{\phi} n。接下来进行一轮配对,在这hh棵半树中最少进行(hlogϕn1)/2(h-\log_{\phi}n-1)/2fair-link\operatorname{fair-link},每一次会把一个根变成1,11,1-结点,使势减少11,因此配对至少使势减少(hlogϕn1)/2=h/2O(logn)(h-\log_{\phi}n-1)/2=h/2-O(\log n)。整个操作使势增加最多logϕn(h/2O(logn))=O(logn)h/2\log_{\phi}n-(h/2-O(\log n))=O(\log n)-h/2。我们可以把势的单位增加到可以消去实际代价O(h)O(h),因此摊还复杂度是O(logn)O(\log n)
接下来是恶心的decrease-key\operatorname{decrease-key}。假设我们对xx做了一次减值。
如果xx是根,实际代价是O(1)O(1)并且不改变势。
否则,我们就懒得往下分析了 定义u0=left(x),u1=x,ui=fa(ui1)(1<ik)u_0=\operatorname{left}(x), u_1=x, u_i=\operatorname{fa}(u_{i-1})(1<i\leq k)uku_k为在decrease-key\operatorname{decrease-key}中维持rank rule\text{rank rule}时遇到的第一个不需要调整就满足rank rule\text{rank rule}的结点或是根(为根时是调整了根或根的儿子),定义viv_idecrease-key\operatorname{decrease-key}uiu_iui1u_{i-1}之外的儿子,即ui1u_{i-1}的兄弟。加\mathrm{'}表示decrease-key\operatorname{decrease-key}后的(真省事)(就是说,举个例子,rank\operatorname{rank}decrease-key\operatorname{decrease-key}前的rank\operatorname{rank}rank\operatorname{rank'}decrease-key\operatorname{decrease-key}后的rank\operatorname{rank})。
显然,这次操作的实际代价是O(k)O(k)
显然,decrease-key\operatorname{decrease-key}后势可能发生变化的只有u1,...,uku_1,...,u_k
显然,decrease-key\operatorname{decrease-key}后的结构变化只有u2u_2的原来是u1u_1的那个儿子变成了v1v_1u1u_1变成了根并失去了右儿子。
先考虑基本势变化,即u1,...,uku_1,...,u_k的基本势变化。把它分成三部分。我们会在每部分最后列出一个式子,从它们可以推出关于基本势变化的结论。
  1. 考虑v1,u1,u2,...,uk1v_1,u_1,u_2,...,u_{k-1}Δrank\Delta \operatorname{rank}和,在decrease-key\operatorname{decrease-key}前它们在同一条链上,中间的部分拆开相消,得到rank(uk)rank(v1)\operatorname{rank}(u_k)-\operatorname{rank}(v_1)
    (说句闲话,这部分论文似乎出了点问题,把v1v_1写成了v0v_0)
    考虑v1,u2,u3,...,uk1v_1,u_2,u_3,...,u_{k-1}Δrank\Delta \operatorname{rank'}和,在decrease-key\operatorname{decrease-key}后它们在同一条链上,中间的部分拆开相消,得到rank(uk)rank(v1)\operatorname{rank'}(u_k)-\operatorname{rank'}(v_1)
    因为rank(v1)=rank(v1)\operatorname{rank'}(v_1)=\operatorname{rank}(v_1)rank(uk)rank(uk)\operatorname{rank'}(u_k)\leq\operatorname{rank}(u_k)rank(uk)rank(v1)rank(uk)rank(v1)\operatorname{rank'}(u_k)-\operatorname{rank'}(v_1)\leq\operatorname{rank}(u_k)-\operatorname{rank}(v_1)。这部分的基本势减少了至少00。(这是uiu_iui+1u_{i+1}的贡献,decrease-key\operatorname{decrease-key}v1v_1u1u_1的贡献和decrease-key\operatorname{decrease-key}v1v_1u2u_2的贡献)
  2. 显然,Δrank(u0)=1Δrank(u0)+1\Delta\operatorname{rank'}(u_0)=1\leq\Delta\operatorname{rank}(u_0)+1。这部分的基本势减少了至少00。(这是u0u_0u1u_1的贡献)
  3. 因为uiu_i被调整了而viv_i没有,Δrank(vj)Δrank(vj)1(2j<k)\Delta\operatorname{rank'}(v_j)\leq\Delta\operatorname{rank}(v_j)-1(2\leq j< k)。因为uku_k不一定被调整了,Δrank(vk)Δrank(vk)\Delta\operatorname{rank'}(v_k)\leq\Delta\operatorname{rank}(v_k)。这部分的基本势减少了至少k3k-3。(这是viv_iui(2i)u_i(2 \leq i)的贡献)
我们已经计算了u1,...uku_1,...u_k的每个儿子对它们的贡献。把它们加起来就得到基本势减少了至少k3k-3,即增加最多3k3-k
然后考虑额外势变化。考虑额外势只会在1,11,1-结点变成其它结点时,或是某个结点变成根时增加11
我们先证明在被调整的点中最多只有两个1,11,1-结点。考虑调整过程中遇到的第一个1,11,1-结点uju_j,它会被调整为1,21,2-结点或0,?0,?-结点。对于第一种,它的rank\operatorname{rank}没有改变,所以父亲的条件不会被破坏,所以它是最后一个被调整的结点,一共有一个1,11,1-结点。对于第二种,它的rank\operatorname{rank}减少了11,因此接下来所有需要调整点的rank\operatorname{rank}都只会减少11(不理解可以自己枚举情况)。接下来遇到的第二个1,11,1-结点只会被调整为1,21,2-结点,所以它是最后一个被调整的结点,一共有两个1,11,1-结点。因此,1,11,1-结点最多有两个,最多使额外势增加22
decrease-key\operatorname{decrease-key}过程中,只有u1u_1变成了根,使额外势增加11
把基本势和额外势的变化加起来,我们得到势的变化 : 势最多增加6k6-k。我们可以把势的单位增大到可以消去实际代价O(k)O(k),所以decrease-key\operatorname{decrease-key}的摊还复杂度是O(1)O(1)

6.代码实现

请参考放在最前面的EtaoinWu的blog。
或者往下翻,是我自己写的代码,有注释。用指针没有可读性
优化Dijkstra到O(nlogn+m)O(n \log n+m)是支持O(logn)O(\log n)时间insert\operatorname{insert}extract-min\operatorname{extract-min}O(1) decrease-keyO(1) \space\operatorname{decrease-key}的堆的经典应用。
CPP
//Type-2 Rank-Pairing Heap优化Dijkstra
//luogu P4779(请自行调整MAXN和MAXM)

//没有merge操作,写起来大概也不难(

#define MAXN 100000
#define MAXM 1000000
#define Log_Phi_N 29

//点数,边数,桶数量

#ifndef NULL
#define NULL 0
#endif

#include<string.h>
#include<stdlib.h>
#include<stdio.h>

template<class T>

struct RP_Heap
{
	struct ListNode;
	struct Node
	{
		T val;
		int rank;
		Node *l,*r,*fa;
		ListNode* p;
		Node(){}
		Node(T _val,int _rank=0,Node* _l=0,Node* _r=0,Node* _fa=0):val(_val),rank(_rank),l(_l),r(_r),fa(_fa){}
	}t[MAXN+2],*p[MAXN+2],*min,*bucket[Log_Phi_N+1];
	int cnt,siz;
	bool has_min;
	struct ListNode
	{
		Node* ptr;
		ListNode *pre,*nxt;
	};
	struct List//手写链表
	{
		ListNode* head;
		
		List()
		{
			head=new ListNode();
			head->ptr=NULL;
			head->nxt=head;
			head->pre=head;
		}
		
		inline void insert(Node* _ptr)
		{
			ListNode* u=new ListNode();
			u->ptr=_ptr;
			u->pre=head;
			u->nxt=head->nxt;
			head->nxt->pre=u;
			head->nxt=u;
			_ptr->p=u;
		}
		
		inline void remove(ListNode* u)
		{
			u->pre->nxt=u->nxt;
			u->nxt->pre=u->pre;
			u->ptr->p=NULL;
			delete u;
		}
	}list;
	
	RP_Heap()
	{
		for(int i=0;i<MAXN;i++)
			p[i]=&t[i];//p[i]指向t[i],便于分配空间
		has_min=0;//has_min让extract-min之后不用把min赋为-inf
		memset(bucket,0,sizeof(bucket));
	}
	
	inline Node* new_Node(T v)//这句话就是分配一个新结点,val为v的意思(
	{
		return ( & ( * p[cnt++] = Node(v,0,NULL,NULL,NULL) ) );
	}
	
	inline void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
	inline int max(int x,int y){ return x>y?x:y; }
	inline int abs(int x){ return x>0?x:-x; }
	
	inline void swap(Node* &x,Node* &y){ Node* temp=x;x=y;y=temp; }
	
	inline Node* link(Node* u,Node* v)
	{
		if(v->val<u->val) swap(u,v);
		v->r=u->l;
		if(v->r)//记得判断是不是空指针,否则RE,以下类似的省略注释
			v->r->fa=v;
		u->l=v;
		v->fa=u;
		u->rank++;
		return u;
	}
	
	inline Node* update_min(Node* u)//更新min,为了写起来舒服,返回值是给的指针
	{
		if(!has_min||u->val<min->val)
			min=u,has_min=1;
		return u;
	}
	
	inline Node* insert(T _val)
	{
		Node* u=new_Node(_val);
		list.insert(update_min(u));//看出来update_min这么写舒服了qwq
		siz++;
		return u;
	}
	
	inline T find_min(){ return min->val; }
	inline int size(){ return siz; }
	inline bool empty(){ return siz==0; }
	
	inline void extract_min()
	{
		siz--;
		list.remove(min->p);
		has_min=0;
		int rk;
		Node *next_node,*u;
		ListNode *first=list.head->nxt;//第一个原有根位置
		for(u=min->l;u;u=next_node)//拆分左子树右链
		{
			next_node=u->r;
			u->fa=u->r=NULL;
			rk=u->rank;
			if(bucket[rk])//和桶里的合并,扔进根链表
				list.insert(update_min(link(u,bucket[rk]))),bucket[rk]=NULL;
			else//扔进桶
				bucket[rk]=u;
		}
		for(ListNode *i=first,*next_node;i!=list.head;i=next_node)
		{
			u=i->ptr;
			rk=u->rank;
			next_node=i->nxt;
			list.remove(i);
			if(bucket[rk])
				list.insert(update_min(link(u,bucket[rk]))),bucket[rk]=NULL;
			else
				bucket[rk]=u;
		}
		for(int i=0;i<=Log_Phi_N;i++)//遍历桶,把桶里的扔进根链表
			if(bucket[i])
				list.insert(update_min(bucket[i])),bucket[i]=NULL;
	}
	
	inline void decrease_key(Node* u,T key)//把u的值变成k(所以需要自己保证是减值不是增值)
	{
		u->val=key;
		if(u->fa==NULL)//是根,更新min直接返回
		{
			update_min(u);
			return;
		}
		if(u->l) u->rank=u->l->rank+1;//更新rank为左儿子rank+1
		else u->rank=0;//没有左儿子,说明是单点
		if(u->r) u->r->fa=u->fa;//把右儿子接到自己原来的位置
		if(u==u->fa->l) u->fa->l=u->r;
		else u->fa->r=u->r;
		int temp,lrk,rrk;
		for(Node* v=u->fa;v;v=v->fa)
		{
			lrk=v->l?v->l->rank:-1;//实现的小技巧,没有结点的rank视为-1
			rrk=v->r?v->r->rank:-1;
			temp=max(lrk,rrk)+(abs(lrk-rrk)<=1?1:0);//根据rank rule计算新的rank
			if(temp==v->rank)//如果没有改变rank,break掉
				break;
			v->rank=temp;
		}
		u->fa=u->r=NULL;//最后断开父亲和右儿子是因为前面循环开头要用右儿子,懒得再搞临时变量了
		list.insert(update_min(u));//插入根链表并更新min
	}
};

//下面的是Dijkstra模板

struct Edge
{
	int v,w,next;
}e[2*MAXM+2];

struct Node
{
	int u;
	int dis;
	inline bool operator<(const Node &x) const
	{
		return this->dis<x.dis;
	}
};

int h[MAXN+2],cnt=0;

inline void add_edge(int u,int v,int w)
{
	e[++cnt]={v,w,h[u]};
	h[u]=cnt;
}

int dis[MAXN+2];
int n,m,start,tu,tv,tw,to;

RP_Heap<Node> q;
RP_Heap<Node>::Node* node[MAXN+2];//对每个堆结点保留一个指针,才能进行decrease-key

inline void dij()//Dijkstra板子
//没有vis(表示是否已经扩展过的数组)是因为我们有decrease-key
{
	int now;
	for(int i=1;i<=n;i++)
		dis[i]=i==start?0:0x7fffffff,node[i]=q.insert({i,dis[i]});
	while(!q.empty())
	{
		now=q.find_min().u;
		q.extract_min();
		for(int i=h[now];i!=0;i=e[i].next)
			if(dis[e[i].v]>dis[now]+e[i].w)
				dis[e[i].v]=dis[now]+e[i].w,
				q.decrease_key(node[e[i].v],{e[i].v,dis[e[i].v]});//减值
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&start);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&tu,&tv,&tw),add_edge(tu,tv,tw);
	dij();
	for(int i=1;i<=n;i++)
		printf("%d ",dis[i]);
	return 0;
}

7.说在后面&更新记录

看到这篇blog的dalao们,如果发现了什么问题,希望可以告诉我qwq
2020/4/15 完结撒花qwq
upd 2020/4/16: 增加了代码,修改了一些细节。
upd 2020/4/17: 修改了一些细节。连更三天可见有多少bug
upd 2020/4/22: 修改了一处错误。
upd 2020/5/22: 修改了一些细节。
upd 2020/9/20: 修改了一处错误。

评论

37 条评论,欢迎与作者交流。

正在加载评论...