突然觉得堆优化$O(log_n)$的复杂度很优啊,然而第n次忘记了$Dijistra$怎么写QAQ发现之前都是用的手写堆,这次用一下$stl$

#include<bits/stdc++.h>
#define LL long long
using namespace std; int n, m; struct Node {
int v, nex, w;
Node(int v = , int nex = , int w = ) :
v(v), nex(nex), w(w) { }
} Edge[]; int h[], stot;
void add(int u, int v, int w) {
Edge[++stot] = Node(v, h[u], w);
h[u] = stot;
} struct QAQ {
int u; LL dis;
QAQ(int u = , LL dis = ) :
u(u), dis(dis) { }
bool operator < (const QAQ a) const {
return dis > a.dis;
}
}; LL dis[];
bool flag[];
void Diji(int s) {
priority_queue < QAQ > q;
for(int i = ; i <= n; i ++) dis[i] = 0x3f3f3f3f;
dis[s] = ;
q.push(QAQ(s, ));
while(!q.empty()) {
QAQ x = q.top(); q.pop();
int u = x.u;
if(flag[u]) continue;
flag[u] = ;
for(int i = h[u]; i; i = Edge[i].nex) {
int v = Edge[i].v;
if(!flag[v] && dis[v] > dis[u] + Edge[i].w) {
dis[v] = dis[u] + Edge[i].w;
q.push(QAQ(v, dis[v]));
}
}
}
} int main() {
int s;
scanf("%d%d%d", &n, &m, &s);
for(int i = ; i <= m; i ++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
Diji(s);
for(int i = ; i <= n; i ++)
printf("%lld ", dis[i]);
return ;
}

然而遇到了这道题...

3040: 最短路(road)

Time Limit: 60 Sec  Memory Limit: 200 MB
Submit: 4331  Solved: 1387
[Submit][Status][Discuss]

Description

N个点,M条边的有向图,求点1到点N的最短路(保证存在)。
1<=N<=1000000,1<=M<=10000000

Input

第一行两个整数N、M,表示点数和边数。
第二行六个整数T、rxa、rxc、rya、ryc、rp。

前T条边采用如下方式生成:
1.初始化x=y=z=0。
2.重复以下过程T次:
x=(x*rxa+rxc)%rp;
y=(y*rya+ryc)%rp;
a=min(x%n+1,y%n+1);
b=max(y%n+1,y%n+1);
则有一条从a到b的,长度为1e8-100*a的有向边。

后M-T条边采用读入方式:
接下来M-T行每行三个整数x,y,z,表示一条从x到y长度为z的有向边。

1<=x,y<=N,0<z,rxa,rxc,rya,ryc,rp<2^31

Output

一个整数,表示1~N的最短路。

Sample Input

3 3
0 1 2 3 5 7
1 2 1
1 3 3
2 3 1

Sample Output

2

HINT

【注释】

请采用高效的堆来优化Dijkstra算法。

Source

[Submit][Status][Discuss]

HOME Back


普通的堆优化会$MLE$,因为优先队列会把一个点弄进去很多次。

在网上发现了配对堆这个东西,可以包含优先队列的所有操作!!$tql$!!!


以下转载大米饼的博客:

[产品特色]

      ①沛堆堆(乱取的绰号)是一颗多叉树。

      ②包含Priority_Queue的所有功能,可用于优化最短路。

      ③属于可并堆,因此对于集合合并维护最值的问题很实用。

      ④速度快于一般的堆结构(左偏树,斜堆,随机堆……),具体快在这里:

      这里就顺带引出它的基本操作啦:

        ·合并(Merge): O(1)

        ·插入(Insert/Push): O(1)

        ·修改值(Change): O(1)/O(logn)

        ·取出维护的最值(Top): O(1)

        ·弹出堆顶元素(Pop): O(logn)

[功能介绍]

       ①可并堆的灵魂——Merge操作

      这里令人惊奇的是,配对堆只需要O(1)的时间完成这一步,具体做法为比较两个需要合并的堆的根的权值大小,然后就将那优先级较低(比如你要求大的在堆顶,那么权值越大,优先级越高)置为另一个点的儿子,即fa[v]=u,再将u向v建边即可。

       ②经典操作之一——插入(Push/Insert)操作

就很容易了,将插入元素新建为一个单独的节点作为一个堆,与当前的堆进行Merge操作就可以了。

       ③经典操作之二——取最值(Top)操作

       就直接用Root记录堆根,然后返回val[Root]就美妙完成任务。

       ④重要而具有特色的操作——修改操作(Change)

       修改一个节点的的权值,那么怎么处理来继续保持配对堆的堆性质?首先将这个点和父节点的连边断掉,即fa[u]=0(由于父节点连边使用链式前向星,不方便删除,就不删除,但是这样并不会影响正确性,因为后文枚举一个点的儿子节点时,要确认某个点是它的儿子节点,不仅是要这个点能够有边指向这个儿子,同时需要这个儿子的fa[]中存储的就是这个节点)。

       断掉与父亲的连边后,相当于形成两个堆,接下来进行一次Merge操作就好了。可以发现这个操作的时间复杂度是O(1),但有资料认为这个操作可能会破坏配对堆应有的结构(这"应有"的结构在下文会体现出来,它是Pop操作是O(logn)而不是O(n)的重要保证),结构改变后就会影响Pop的复杂度,使其向 O(n)退化,因此计算后认定其实修改操作从时间复杂度贡献分析来看,可能是O(logn)而不是O(1)。

       ⑤最缓慢但很重要的操作——弹出最值(Pop)操作

            你会发现上文的操作都那么偷懒,几乎都是胡乱Merge一下,Merge函数又是随随便便连一条边就完事儿了……因此这个操作需要来收拾这个烂摊子。我们现在的任务是删除根节点,那么我们就要从它的儿子中选出合法继承人。如果直接将所有儿子挨个挨个Merge起来,那么这样很容易使得一个点有很多个儿子,从而影响后来的Pop操作时间,将O(logn)退化为O(n)。较快的做法是将子树两两合并,不断这样合并,最终形成一棵树,同理,这样之所以快是因为保证了后面pop操作时候点的儿子个数不会太多。

[要点尝鲜]

   ①链式前向星建边:

②Merge操作:

   ③Insert/Push操作:

   ④ChangeVal操作:

   ⑤Top操作:

   ⑥Pop操作:

[产品代码]

      接下来一份简洁的代码,内容是将n个数排序。

      其中的Stack是用来回收空间的。这里没有给出ChangVal函数,原因是这个函数适用于有特定位置的元素的修改,比如将数组插入堆,然后修改数组下表为i的元素权值。上文内容毫无保留地讲述了ChangVal的内容,直接打就是了。

       同样的,如果要用来维护一些信息,比如Dijkstra的优化,那就在点的信息上添加记录最短路中点的编号之类的形成映射以达成快速取值的目的,其实呢和STL优先队列是一样的。

#include<stdio.h>
#define go(i,a,b) for(int i=a;i<=b;i++)
#define fo(i,a,x) for(int i=a[x],v=e[i].v;i;i=e[i].next,v=e[i].v)
const int N=;
int n,a[N],b[N]; struct Stack
{
int S[N],s=,k=;
int get(){return s?S[s--]:++k;}
void Save(int index){S[++s]=index;}
}Node,Edge; struct Pairing_Heap
{
int sz=,fa[N],head[N],k,val[N],Root;
int S[N],s;struct E{int v,next;}e[N]; void ADD(int u,int v){e[k=Edge.get()]=(E){v,head[u]};head[u]=k;}
int Merge(int u,int v){val[u]>val[v]?u^=v^=u^=v:;ADD(fa[v]=u,v);return u;}
void Push(int Val){int u=Node.get();val[u]=Val;Root=Root?Merge(Root,u):u;}
int Top(){return val[Root];} void Pop()
{
s=;fo(i,head,Root)Edge.Save(i),fa[v]==Root?fa[S[++s]=v]=:;
fa[Root]=head[Root]=;Node.Save(Root);Root=;
int p=;while(p<s){++p;if(p==s){Root=S[p];return;}
int u=S[p],v=S[++p];S[++s]=Merge(u,v);}
}
}q;
int main()
{
scanf("%d",&n);
go(i,,n)scanf("%d",a+i),q.Push(a[i]);
go(i,,n)printf("%d\n",q.Top()),q.Pop();return ;
}//Paul_Guderian

大米飘香的总结:

沛堆堆继承了斐波拉契堆的优秀操作复杂度,同时相比之下降低了空间复杂度和代码复杂度,这样优美高效的数据结构当然适合用在竞赛领域。如果谈到什么时候会用到沛堆堆,大米饼认为主要是两个方面——代替优先队列和代替常规的可并堆。常规可并堆如斜堆,随机堆和左偏树虽然代码更短,但是时间复杂度不够理想,再说了沛堆堆代码其实也很短的(这使得我们可以直接手写而不用冒风险去调用STL中的沛堆堆了)。最后这篇博文有一个小小的缺陷是,由于大米饼笨笨的,其实上文中ChangeVal是有局限的,其中修改值只能比原值小(如果越小优先级越高的话),因为如果修改为较大值,其操作就类似与Pop了,虽然个人认为时间复杂度由于都是O(logn)不会影响,但毕竟还没试验过,须谨慎使用。如果大米饼出错了或者出现冗余,希望来浏览的人加以指出批评。


然而本人并不打算学透,而且有stl的配对堆,所以背背代码吧~~

#include<bits/stdc++.h>
#include<ext/pb_ds/priority_queue.hpp>
#define LL long long
using namespace std;
using namespace __gnu_pbds; typedef __gnu_pbds::priority_queue < pair < LL, int > > heap;
heap::point_iterator id[]; int n, m; struct Node {
int v, nex, w;
Node(int v = , int nex = , int w = ) :
v(v), nex(nex), w(w) { }
} Edge[]; int h[], stot;
void add(int u, int v, int w) {
Edge[++stot] = Node(v, h[u], w);
h[u] = stot;
} LL dis[];
bool flag[];
void Diji() {
heap q;
for(int i = ; i <= n; i ++) dis[i] = 0x3f3f3f3f;
id[] = q.push(make_pair(, ));
while(!q.empty()) {
int x = q.top().second; q.pop();
if(x == n) break;
for(int i = h[x]; i; i = Edge[i].nex) {
int v = Edge[i].v;
if(dis[v] > dis[x] + Edge[i].w) {
dis[v] = dis[x] + Edge[i].w;
if(id[v] != ) q.modify(id[v], make_pair(-dis[v], v));
else id[v] = q.push(make_pair(-dis[v], v));
}
}
}
} int main() {
scanf("%d%d", &n, &m);
int T, rxa, rxc, rya, ryc, rp;
scanf("%d%d%d%d%d%d", &T, &rxa, &rxc, &rya, &ryc, &rp);
int x = , y = , z = ;
for(int i = ; i <= T; i ++) {
int a, b;
x = ((long long)x * rxa + rxc) % rp;
y = ((long long)y * rya + ryc) % rp;
a = min(x % n + , y % n + );
b = max(y % n + , y % n + );
add(a, b, 1e8 - * a);
}
for(int i = ; i <= m - T; i ++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
Diji();
printf("%lld", dis[n]);
return ;
}

【最短路Dijistra】【一般堆优化】【配对堆优化】的更多相关文章

  1. POJ 3635 - Full Tank? - [最短路变形][手写二叉堆优化Dijkstra][配对堆优化Dijkstra]

    题目链接:http://poj.org/problem?id=3635 题意题解等均参考:POJ 3635 - Full Tank? - [最短路变形][优先队列优化Dijkstra]. 一些口胡: ...

  2. 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)

    关于几个的区别和联系:http://www.cnblogs.com/zswbky/p/5432353.html d.每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个(草儿家到 ...

  3. dijkstra(最短路)和Prim(最小生成树)下的堆优化

    dijkstra(最短路)和Prim(最小生成树)下的堆优化 最小堆: down(i)[向下调整]:从第k层的点i开始向下操作,第k层的点与第k+1层的点(如果有)进行值大小的判断,如果父节点的值大于 ...

  4. 配对堆优化Dijkstra算法小记

    关于配对堆的一些小姿势: 1.配对堆是一颗多叉树. 2.包含优先队列的所有功能,可用于优化Dijkstra算法. 3.属于可并堆,因此对于集合合并维护最值的问题很实用. 4.速度快于一般的堆结构(左偏 ...

  5. 3424:Candies(差分约束,Dijkstra)(配对堆优化

    题面链接 题解 令x-y<=z表示x最大比y大z. 若b-a<=k1, c-b<=k2, c-a<=k3,那么c-a最大为多少呢?显然应该等于min(k1+k2, k3).可以 ...

  6. 《Windows核心编程系列》十四谈谈默认堆和自定义堆

    堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...

  7. 堆和索引堆的实现(python)

    ''' 索引堆 ''' ''' 实现使用2个辅助数组来做.有点像dat.用哈希表来做修改不行,只是能找到这个索引,而需要change操作 还是需要自己手动写.所以只能用双数组实现. #引入索引堆的核心 ...

  8. Eclipse MAT:浅堆 vs 保留堆

    来自:唐尤华 https://dzone.com/articles/eclipse-mat-shallow-heap-retained-heap 有没有想要搞清楚浅堆(Shallow Heap)和保留 ...

  9. 十二、jdk工具之jcmd介绍(堆转储、堆分析、获取系统信息、查看堆外内存)

    目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...

随机推荐

  1. oggMonitor是什么

    goldengate monitor是一套监控goldengate的软件,如果安装的ogg比较多,使用goldengate monitor可以清楚的看见全部OGG的每个进程运行状态,以及整个OGG的架 ...

  2. 【逆向知识】PE ASLR

    1.知识点 微软从windows vista/windows server 2008(kernel version 6.0)开始采用ASLR技术,主要目的是为了防止缓冲区溢出 ASLR技术会使PE文件 ...

  3. ntpdate[35450]: the NTP socket is in use, exiting

    当前主机已是NTP服务器,需关闭当前NTP服务,再同步其他NTP服务器的时间 service ntpd stop 然后ps -ef | grep ntp看进程是否已杀掉 然后再次ntpdate Ser ...

  4. 【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider

    数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供 ...

  5. java基础30 List集合下的LinkedList集合

    单例集合体系: ---------| collection  单例集合的根接口--------------| List  如果实现了list接口的集合类,具备的特点:有序,可重复       注:集合 ...

  6. Codeforces 682C Alyona and the Tree (树上DFS+DP)

    题目链接:http://codeforces.com/problemset/problem/682/C 题目大意:取树上任意一个点v,若点v的子树中有一个点u使得dist(v,u)>a[u]那么 ...

  7. 开源框架:SDWebImage

    http://blog.csdn.net/uxyheaven/article/details/7909373 SDWebImage是我搞iOS以来少数佩服的框架,膜拜一下作者.真的写的非常棒! 这套开 ...

  8. 拉格朗日(Lagrange)插值算法

    拉格朗日插值(Lagrange interpolation)是一种多项式插值方法,指插值条件中不出现被插函数导数值,过n+1个样点,满足如下图的插值条件的多项式.也叫做拉格朗日公式.  这里以拉格朗日 ...

  9. CF 586A 找1的个数和101的个数

    Sample test(s) input 50 1 0 1 1 output 4 input 71 0 1 0 0 1 0 output 4 input 10 output 0 # include & ...

  10. CCF CSP 201509-2 日期计算

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201509-2 日期计算 问题描述 给定一个年份y和一个整数d,问这一年的第d天是几月几日? ...